Interview Prep Zoneby CuriouserLabs

Question 3 of 5 · staff level

Sealed interfaces + pattern matching: how do they enable algebraic data modeling in Java?

🎤 Say this first

sealed lets a supertype list its complete set of implementations (permits), turning a hierarchy into a closed sum type. Combined with records (product types) you get algebraic data types; combined with switch pattern matching (Java 21) you get exhaustive case analysis: the compiler errors if a new variant isn't handled — no default needed, so adding a variant breaks every switch that must care, at compile time. It flips the OO expression problem: easy to add operations, deliberate to add variants.

The full picture

A payment domain as an ADT
sealed interface PaymentResult permits Approved, Declined, Pending {}
record Approved(String authCode, Instant at) implements PaymentResult {}
record Declined(DeclineReason reason, boolean retryable) implements PaymentResult {}
record Pending(String checkUrl) implements PaymentResult {}

String describe(PaymentResult r) {
    return switch (r) {                       // NO default: exhaustive!
        case Approved(String code, _)           -> "OK " + code;
        case Declined(var reason, true)         -> "Retry: " + reason;
        case Declined(var reason, false)        -> "Hard decline: " + reason;
        case Pending(String url)                -> "3DS at " + url;
    };
}   // add 'Refunded' to permits -> THIS switch stops compiling. Feature, not bug.
  • Record patterns deconstruct nested data (case Line(Point(var x1, var y1), Point(var x2, var y2))); guards add conditions (case Declined d when d.retryable()).
  • Sealed ≠ final: permitted subclasses choose final, sealed, or non-sealed (escape hatch — use rarely).
  • Design guidance: model states and events as sealed hierarchies (order states, domain events, parser nodes); keep behavior-rich strategies as open interfaces — closed data, open behavior.
  • Exhaustiveness + no default is the whole point: writing default -> throw throws away the compiler's help.

🔄 Likely follow-up questions

  • How does this compare to Kotlin sealed classes / Scala case classes?
  • When is the visitor pattern still better than a switch?
  • How does exhaustiveness interact with libraries evolving their sealed hierarchies (binary compat)?