TSworkingts-evergreen

Discriminated Unions

Tag your variants.

Give every variant in a union a unique literal "tag" property β€” and TypeScript will narrow on it for free, with exhaustiveness checking thrown in.

The discriminant

Every variant carries a literal-typed property β€” usually kind, type, or tag. Switching on it narrows the union to a single variant inside each branch.

Exhaustiveness for free

Add default: const _: never = x at the end of the switch. The compiler errors if a new variant is added but unhandled β€” refactors stop being scary.

The Result<T, E> pattern

A discriminated union of {ok: true, value} and {ok: false, error} replaces try/catch with values. Forces the caller to handle both cases.

A reducer-style state machine

The same tag that narrows data narrows state. Model each phase as a variant, and a reducer that maps (state, action) produces a new state β€” the compiler stops you reading data while still loading, because that field only exists on the success variant.

A reusable exhaustiveness guard

Hoist the never trick into a one-line helper. Call assertNever(x) in the default branch of any switch and every union that flows through it gets compile-time exhaustiveness β€” add a variant without a case and the build breaks at the call site.

Try it 5 examples

Shape: Circle | Square | Triangle

TSintro
Press Run to execute

Switching on the kind tag narrows each branch to one variant, so s.radius, s.side, and s.base/s.height are only accessible where they exist β€” yielding 78.54, 16, and 6. With every variant handled, TypeScript accepts the function as returning number even without a default.