Sealed ClassEdit

Sealed classes are a construct in several modern programming languages that constrain how a class can be extended. In practice, a sealed class defines a closed set of possible subtypes, and only those subtypes—defined in the same module or file—are allowed to inherit from it. This creates a bounded hierarchy that can be treated as a discriminated union or sum type, where every possible form of data in a given context is known to the compiler.

The core idea is not exotic magic but a disciplined approach to modeling domain concepts. By limiting inheritance, sealed classes help ensure that when code branches on the type of an object, the compiler can check that all cases are handled. This reduces the risk of new, unanticipated types appearing in a program and breaking existing logic. In languages that support it, this also enables exhaustive pattern matching, safer refactoring, and clearer API boundaries. See how this relates to the broader ideas of Algebraic data type design and Pattern matching.

Core concepts

  • Definition and scope: A sealed class restricts subclassing to a defined set of types, typically declared in the same module or file. This creates a fixed hierarchy that the compiler can reason about at compile time.
  • Exhaustiveness: When code branches on the specific subtype of a sealed class, the compiler can enforce that all possible cases are covered, aiding correctness. See for example Pattern matching in languages that support it.
  • API boundaries: Sealed hierarchies encourage stable, well-understood APIs. Consumers can rely on a finite set of subtypes, which makes versioning and maintenance less risky.
  • Relationship to sum types: Sealed classes are a mechanism for implementing Sum type behavior, where a value can be one of several distinct forms, each carrying different data.

Language-specific implementations

  • Kotlin: In Kotlin, a sealed class (and the corresponding sealed interface) provides a closed hierarchy that is especially suited for modeling domain events, results, or other discriminated unions. The compiler can ensure that when expressions over a sealed type are exhaustive, all branches are accounted for. See Kotlin.
  • Java: Java introduced sealed classes and interfaces in recent versions, with a permits clause that explicitly lists allowed subclasses. This brings the same kind of safety to Java codebases, aiding maintainability and evolvability. See Java.
  • C#: In C#, the approach differs in syntax and semantics, but the goal is similar: limit inheritance to a defined set of types to improve safety and understandability. See C#.

Other languages that experiment with or implement similar capabilities include those that care deeply about type safety and explicit modeling of data, often within their own Type system paradigms.

Benefits

  • Safety and stability: By restricting extension, sealed hierarchies reduce the chance of accidental or unapproved changes introducing bugs. This is particularly valuable in large codebases with many contributors.
  • Predictable maintenance: When a library exposes a sealed type, maintainers know exactly which subtypes exist, simplifying deprecation, refactoring, and API evolution.
  • Safer pattern matching: Exhaustive handling of all possible cases reduces runtime errors and makes code paths easier to reason about.
  • Performance implications: In some languages, the closed set of types enables optimizations at compile time, since the runtime type can be determined more directly during dispatch.

Controversies and debates

  • Extensibility vs safety: Critics argue that sealing a hierarchy can make it harder for users to extend a library with new variants, potentially slowing innovation or forcing workarounds. Proponents counter that the trade-off is warranted by the gains in stability and clarity, especially in long-lived systems.
  • Library evolution and compatibility: A sealed design can impose more ceremony when extending a system, since new subtypes may require coordinated changes across modules or libraries. Advocates stress that clear versioning and well-communicated deprecation policies mitigate risk, and that controlled extension often reduces long-term maintenance costs.
  • Testing and mocking: Some teams worry that sealed hierarchies complicate testing because the set of possible types is finite, which can complicate test doubles or mocks. In practice, this is often offset by the verifiability of exhaustive branches and by targeted test coverage of each known subtype.
  • Interoperability across languages and ecosystems: The benefits of a sealed hierarchy in one language may be harder to realize when sharing components with languages that do not enforce the same constraints. Teams often compensate with clear API contracts and language-agnostic interfaces or with bridging patterns.
  • Alternative design philosophies: Some argue for more open inheritance models or for using interfaces and composition to achieve flexibility. Proponents of sealed designs emphasize that in many domains, clarity, safety, and predictable evolution justify the trade-offs.

See also