type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.size ** 2;
default: {
const _exhaustive: never = s;
return _exhaustive;
}
}
}
A discriminated union is a union of object types that all share one property, here kind. Each member gives that property a different literal value. When you check the property, TypeScript narrows the value to exactly one member. So inside case "circle", TypeScript knows radius exists.
To make the switch exhaustive, assign the value to a variable of type never in the default branch. never is the type with no possible values.
- When every case is handled, nothing is left for
default. Sosis narrowed toneverthere, and the assignment is fine. - Now someone adds
{ kind: "triangle" }and forgets a case. Indefault,sis now the triangle type. - The triangle type cannot be assigned to
never. So the build fails and points at the missing case.
This pattern is common for reducer actions, API states and UI variants.
- Every member of a discriminated union has a shared literal property, like
kind,typeorstatus. - Checking that property narrows the value to one member.
- The
defaultbranch assigns the value to a variable of typenever. - Adding a new member without a case then becomes a compile error.
Read more