Advanced TypeScript

Discriminated Unions

A discriminated union models alternatives using a shared literal field such as kind, type, or status. It makes invalid combinations unrepresentable and gives TypeScript reliable narrowing.
  • One field identifies the variant
  • Model states as alternatives, not flags
  • Payload belongs only where valid
  • Exhaustiveness protects future changes
  • Discriminants work beautifully with reducers and events
Async state as a discriminated union
type LoadState<T> =
  | { kind: "idle" }
  | { kind: "loading" }
  | { kind: "success"; data: T }
  | { kind: "error"; error: Error }

function message(state: LoadState<User>) {
  switch (state.kind) {
    case "idle": return "Not loaded"
    case "loading": return "Loading..."
    case "success": return state.data.name
    case "error": return state.error.message
  }
}
Each branch gets only the fields that make sense for that state.
Flags vs union
ApproachProblem/Benefit
Boolean flagsCan represent impossible states
Optional fieldsCaller must guess which fields exist
Discriminated unionOnly valid states are representable
assertNeverFuture variants force handling
Sources
  • The TypeScript HandbookDiscriminated unions
  • Effective TypeScript, 2nd EditionType design
  • TypeScript ReferenceNarrowing