Prompt
Define a type Shape that is either Circle ({ kind: 'circle', radius: number }) or Square ({ kind: 'square', side: number }). Write a function area that narrows the type on the discriminant and returns the area. Handle unknown kinds explicitly — in TypeScript that is an exhaustiveness check with never; at runtime, throw.
Solution
Discriminated unions are TypeScript's answer to "this value is one of N shapes": a shared literal field (kind) acts as the tag, and control flow narrows on it. Inside case 'circle' the compiler knows shape is Circle, so shape.radius typechecks and shape.side is an error — narrowing is just the compiler following the same runtime check your code already makes.
The discriminant matters doubly because types are erased at compile time: at runtime, kind is the only dispatch mechanism the running code has. One field serves both worlds — that's the design insight this question probes.
The exhaustiveness trick is the senior layer: in the default branch, const _never: never = shape compiles only if every variant is handled — add a Triangle to the union next quarter and every switch you forgot becomes a compile error instead of a silent undefined. The runtime throw covers the other boundary: data arriving from an API was never checked by the compiler at all.
Red flag: casting your way through ((shape as Circle).radius) or sniffing structure with property checks when a discriminant exists. Assertions silence the compiler exactly where the union was supposed to protect you.
Say it: "A discriminated union gives me one literal tag that narrows at compile time and dispatches at runtime — and a never check in the default branch turns a forgotten variant into a compile error."