never Type and Exhaustiveness
What never means, how exhaustive switches use it, and why unreachable code and empty unions show up in TypeScript errors.
- typescript
- never-type
never is the type of values that cannot exist. A function that always throws returns never. A switch branch that TypeScript proves unreachable gets never. Empty unions collapse toward never. That makes never the tool for exhaustiveness checking.
Functions that never return
function fail(message: string): never {
throw new Error(message);
}
function infinite(): never {
while (true) {
/* … */
}
}
never is assignable to every type (bottom type), but almost nothing is assignable to never except never itself.
Exhaustive switch
type Status = 'idle' | 'loading' | 'success' | 'error';
function assertNever(x: never, message?: string): never {
throw new Error(message ?? `Unexpected: ${JSON.stringify(x)}`);
}
function label(status: Status): string {
switch (status) {
case 'idle':
return 'Idle';
case 'loading':
return 'Loading';
case 'success':
return 'Done';
case 'error':
return 'Error';
default:
return assertNever(status);
}
}
Add 'stale' to Status and the default fails: status is no longer never. That compile error is the feature.
Discriminated unions
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'square'; size: number }
| { kind: 'rect'; w: number; h: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.r ** 2;
case 'square':
return shape.size ** 2;
case 'rect':
return shape.w * shape.h;
default:
return assertNever(shape);
}
}
See discriminated unions for UI state.
Filtering to never
type NonNullable<T> = T extends null | undefined ? never : T;
// string | null → string (null branch becomes never and drops out)
Union members that map to never disappear — the basis of Exclude / Extract.
Unreachable code
function example(x: string | number) {
if (typeof x === 'string') {
return x.toUpperCase();
}
if (typeof x === 'number') {
return x.toFixed(2);
}
// x is never here if both branches covered
const _check: never = x;
}
never[] and empty arrays
const empty = []; // often never[] under strict inference in some contexts
const emptyOk: string[] = [];
Annotate empty arrays you plan to push into.
never vs void vs unknown
| Type | Meaning |
|---|---|
void |
Returns nothing useful (can still return undefined) |
never |
Does not complete normally |
unknown |
Some value, unchecked yet |
function log(): void {
console.log('hi');
}
Don’t annotate throwing helpers as void if you want callers to treat them as non-returning control flow.
Footguns
- Missing default — switch may not exhaust under
noImplicitReturnsdepending on flags. - Widening discriminant to
string— exhaustiveness breaks. - Throwing
assertNeveraway in production — still throw; silent ignore hides bugs. - Using
neverfor “TODO types” — preferunknownat boundaries.
Interview out-loud answer
“never means no possible value. I use assertNever in the default branch of a switch over a union so new variants cause compile errors. It’s also the bottom type in conditional filtering like NonNullable.”
Related on this site
- Discriminated unions for UI state
- Type narrowing
- Conditional types intro
- Union and intersection types
- Literal types
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.