"The single most useful pattern in TypeScript. Once you see it, you find places to use it every day."
What a discriminated union is
A discriminated union is a union of object types where every variant carries a property with a literal type — the discriminator — whose value is unique to that variant. The compiler uses the discriminator to narrow the whole object when you check it.
The classic example: shapes.
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'rect'; w: number; h: number };
Every variant has a kind field whose value is a literal string unique to that variant. When you check if (shape.kind === 'circle'), the compiler narrows shape to just the circle variant — and you can safely read shape.radius.
Why this pattern is so good
Discriminated unions model real domain concepts cleanly: states (idle/loading/done/error), events (click/keypress/scroll), API responses (success/error), shapes (circle/square/rect). Anywhere data has 'this OR that, with shape that depends on which,' a discriminated union is the right model.
The combined effect: the compiler keeps the variants distinct, narrowing happens automatically inside if/switch, and exhaustiveness can be enforced via never (next lesson). The pattern scales from 2 variants to 20+ without losing clarity.
Discriminator field conventions
The discriminator field is conventionally named kind, type, or tag. Pick one and use it consistently across your codebase. React's reducer pattern uses type. Functional-language ports often use tag. Domain-specific projects pick kind to avoid colliding with JavaScript's built-in type semantics. The choice is style — consistency matters more than the specific name.
Pippa's confession
kind or status. The pattern is so much a default that I notice when something isn't one: 'why is this a plain object with optional fields instead of a discriminated union?' Usually it should have been.