TypeScript models the same state as a discriminated union: object types that share a literal status field. Exhaustiveness is opt-in, through a default branch that assigns the value to never. Here the loading case is missing, and tsc --strict (TypeScript 7.0.2) reports it.
type LoadState<T> =
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
function render(state: LoadState<string[]>): string {
switch (state.status) {
case "success": return state.data.join(", ");
case "error": return `${state.message} [Retry]`;
default: { const unhandled: never = state; return unhandled; }
}
}Output
state.ts(9,22): error TS2322: Type '{ status: "loading"; }' is not assignable to type 'never'.| Aspect | Kotlin sealed type | TypeScript union |
|---|---|---|
| Case identity | A class per case, tested with is | A literal tag field |
| Exhaustiveness | Built into when | Opt-in never check |
| At run time | Real JVM classes | Types erased; only the tag remains |
| Conditional cases | Guards: is X if ... | Extra if or a matching library |
For Kotlin-style matching in TypeScript, the MIT-licensed ts-pattern (https://github.com/gvergnaud/ts-pattern 15,170 ) (npm 2,036 install ts-pattern, 5.9.0) adds match(state).with(...).exhaustive().