Exhaustiveness Checking with never
Catch unhandled cases at compile time with never.
Exhaustiveness Checking with never is a free TypeScript Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Exhaustiveness Problem
When you add a new union member, it is easy to forget to handle it somewhere. Exhaustiveness checking turns that oversight into a compile error.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
// If we add "triangle" later, we want every switch to complain.The never Type
The never type represents values that can never occur. If every case is handled, the value reaching the default branch has type never.
function fail(): never {
throw new Error("unreachable");
}
// never is assignable to nothing except never itself.Assigning to never in the Default Case
In the default branch, assign the value to a never variable. If all variants are handled, the assignment compiles; if not, it errors.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.side ** 2;
default:
const _exhaustive: never = s;
return _exhaustive;
}
}
console.log(area({ kind: "square", side: 3 }));What Happens When You Forget a Case
If you add a triangle member but forget its case, s in the default is no longer never, so the assignment fails at compile time.
// type Shape = ... | { kind: "triangle"; base: number; height: number };
// Now in default, s is { kind: "triangle"; ... }
// const _exhaustive: never = s; // Error: triangle not assignable to neverThe assertNever Helper
A reusable assertNever function centralizes the pattern. It accepts never and throws, documenting that the branch should be unreachable.
function assertNever(value: never): never {
throw new Error("Unhandled case: " + JSON.stringify(value));
}
console.log(typeof assertNever);Using assertNever in a Switch
Call assertNever(s) in the default case. It enforces exhaustiveness at compile time and gives a clear runtime error if reached.
function assertNever(value: never): never {
throw new Error("Unhandled: " + JSON.stringify(value));
}
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.radius ** 2;
case "square": return s.side ** 2;
default: return assertNever(s);
}
}
console.log(area({ kind: "circle", radius: 1 }).toFixed(2));Compile-Time vs Runtime Safety
The never check catches missing cases before you run the code, and the thrown error protects you if something slips past type checking at runtime.
function assertNever(x: never): never {
throw new Error("Unhandled: " + String(x));
}
// Compile error if a case is missing; runtime throw as a backstop.
console.log("two layers of safety");Exhaustiveness Without a Default
If your function has an explicit return type and a switch covers every case, TypeScript can also flag a missing return, another form of exhaustiveness.
type Light = "red" | "yellow" | "green";
function next(l: Light): Light {
switch (l) {
case "red": return "green";
case "yellow": return "red";
case "green": return "yellow";
}
// No default needed; all cases return.
}
console.log(next("red"));Exhaustiveness With if/else Chains
The same idea works with if/else. After handling each variant, the final else receives a never value.
function assertNever(x: never): never { throw new Error("bad"); }
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function name(s: Shape): string {
if (s.kind === "circle") return "circle";
else if (s.kind === "square") return "square";
else return assertNever(s);
}
console.log(name({ kind: "square", side: 2 }));Why never Is the Right Tool
Because never is assignable to nothing, any leftover variant breaks the assignment. This makes never the perfect detector for unhandled cases.
// Only never is assignable to never.
let x: never;
// x = "hi"; // Error
// Any concrete leftover type fails the same way.
console.log("never catches gaps");Exhaustiveness as a Refactoring Safety Net
With assertNever everywhere, adding a union member produces a tidy list of compile errors pointing exactly at every place you must update.
function assertNever(x: never): never { throw new Error("unhandled"); }
type Status = "idle" | "busy";
function render(s: Status): string {
switch (s) {
case "idle": return "Idle";
case "busy": return "Busy";
default: return assertNever(s);
}
}
console.log(render("idle"));Quick Check: Exhaustiveness
Test your understanding of exhaustiveness checking.
Recap: Exhaustiveness With never
You learned to assign the default value to never (or pass it to assertNever) so the compiler forces you to handle every variant. This turns forgotten cases into compile errors.
function assertNever(x: never): never { throw new Error("unhandled"); }
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function f(s: Shape) {
switch (s.kind) {
case "circle": return s.radius;
case "square": return s.side;
default: return assertNever(s);
}
}
console.log(f({ kind: "circle", radius: 5 }));Frequently asked questions
Is the “Exhaustiveness Checking with never” lesson free?
Yes — the full text of “Exhaustiveness Checking with never” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.
What will I learn in “Exhaustiveness Checking with never”?
Catch unhandled cases at compile time with never. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start TypeScript Academy?
No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Exhaustiveness Checking with never” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this TypeScript Academy lesson?
Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Building Discriminated Unions
- Narrowing on the Discriminant
- Exhaustiveness Checking with never
- Modeling State Machines