Exhaustiveness Checking
In this page:
The never Type
The never type represents values that can never actually occur, which means a function parameter typed as never can be used as a compile-time trap to detect any union member you forgot to handle.
Example: The never Type
function assertUnreachable(x: never): never {
throw new Error("Unhandled case: " + x);
}
type Status = "active" | "inactive";
function check(s: Status) {
if (s === "active") return "on";
if (s === "inactive") return "off";
return assertUnreachable(s);
}
console.log(check("active"));
Exhaustive Switch Statements
Discriminated unions are ideal for exhaustive switch statements, since each case in the switch can correspond to exactly one specific object variant identified by its discriminant property.
Example: Exhaustive Switch Statements
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
}
}
console.log(area({ kind: "square", side: 3 }));
Exhaustive if Statements
Exhaustiveness can also be checked without a switch — narrow away every known union member with if/else branches, then pass whatever's left to a function typed to accept only never.
Example: Exhaustive if Statements
function assertNever(x: never): never {
throw new Error("Unexpected: " + x);
}
type Status = "on" | "off";
function toggle(s: Status): Status {
if (s === "on") return "off";
if (s === "off") return "on";
return assertNever(s);
}
console.log(toggle("on"));
Exhaustiveness in Functions
Returning never from what should be an impossible branch (like the default case of a switch) helps guarantee that a function genuinely handles every member of a union, not just the ones the author remembered.
Example: Exhaustiveness in Functions
type Status = "active" | "inactive";
function label(s: Status): string {
switch (s) {
case "active": return "Active";
case "inactive": return "Inactive";
default:
const _exhaustive: never = s;
return _exhaustive;
}
}
console.log(label("active"));
Benefits of Exhaustiveness
Exhaustiveness checks make union-based code much safer as an application evolves — if a new member is added to the union later, the compiler immediately flags every switch or if-chain that needs updating.
Example: Benefits of Exhaustiveness
type Status = "active" | "inactive" | "pending";
function assertNever(x: never): never {
throw new Error("Unhandled: " + x);
}
function label(s: Status): string {
if (s === "active") return "Active";
if (s === "inactive") return "Inactive";
if (s === "pending") return "Pending";
return assertNever(s);
}
console.log(label("pending"));
Chapter Quiz — Complete all 20 topics to unlock
0/20 topics done
Complete these topics first:
- TypeScript Advanced Types
- Mapped Types
- Conditional Types
- Custom Type Guards
- Assertion Functions
- Control Flow Analysis
- Exhaustiveness Checking
- Satisfies Operator
- Template Literal Types
- Utility Types - Partial
- Utility Types - Required
- Utility Types - Readonly
- Utility Types - Pick
- Utility Types - Omit
- Utility Types - Record
- Utility Types - Exclude and Extract
- Utility Types - NonNullable
- Utility Types - ReturnType
- Utility Types - Parameters
- TypeScript 5 Updates