TypeScript · Types that pay for themselves
How do you get exhaustiveness checking on a union?
Short answer
Exhaustiveness checking uses never to prove every union member is handled. If a new variant is added, the assignment to never fails at compile time, pointing to the missing case.
A switch over a union is exhaustive when every member has a case. The compiler can prove that only if the default branch receives a value of type never. A small assertNever helper turns that proof into a compile-time error when a new variant appears.
type Status = "idle" | "loading" | "success" | "error";
function assertNever(value: never): never {
throw new Error("Unhandled status: " + String(value));
}
function label(status: Status): string {
switch (status) {
case "idle":
return "Idle";
case "loading":
return "Loading";
case "success":
return "Done";
case "error":
return "Failed";
default:
return assertNever(status);
}
}Why it works
- After every case, the remaining type is never
- Passing never to a parameter typed never is valid
- Adding a new member makes the remaining type non-never and breaks the call
- The error points at the missing case, not at the new member
- The same pattern works with if-else chains and reducer actions
Exhaustiveness checking is valuable in reducers, message handlers and any switch over a union that is expected to grow. It turns a silent runtime fallthrough into a build failure. The helper must actually throw at runtime, because the compiler only checks the types.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1Why does the remaining type become never?
- 2What happens when a new union member is added?
- 3Can you use exhaustiveness checking with if-else?
- 4Why is a fallback default harmful here?
Say it in the interview
Exhaustiveness checking makes the compiler prove that every union member is handled. I route the default branch into a function that accepts never; if a new variant appears, the call no longer type-checks. That turns a runtime fallthrough into a build error. I use it in reducers, message handlers and anywhere a union is likely to grow.
Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.
Related questions
Reading is step one
Now say it out loud.
Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live TypeScript mock interview and get scored feedback on depth, clarity and confidence.