The never Type
never is TypeScript’s bottom type — the type that has no values at all. Nothing is assignable to never (except never itself), yet never is assignable to every type, making it the mirror image of unknown. It represents situations that can never happen: a function that never returns, a variable that has been narrowed to nothing, or a branch the compiler has proven is unreachable. Far from being academic, never powers one of TypeScript’s most valuable patterns — compile-time exhaustiveness checking. This page covers where never comes from and how to put it to work.
Functions that never return
A function that always throws or loops forever has return type never, because control never reaches the end and no value is ever returned.
function fail(message: string): never {
throw new Error(message);
}
function loopForever(): never {
while (true) {
/* never returns */
}
}
Note the difference from void: a void function returns normally but yields no useful value, whereas a never function never returns at all. TypeScript uses this distinction in control-flow analysis — after a call to fail(), code on the following line is treated as unreachable. Contrast with The void Type.
Impossible narrowed types
When narrowing eliminates every member of a type, what remains is never. The compiler reaches this when no possible value could satisfy the conditions.
function check(x: string | number) {
if (typeof x === "string") {
// x: string
} else if (typeof x === "number") {
// x: number
} else {
// x: never — no remaining possibilities
}
}
type Impossible = string & number; // never — no value is both
In the final else, every variant has been handled, so x narrows to never. Likewise, an intersection of incompatible types like string & number collapses to never because no value can belong to both.
Exhaustiveness checking
The flagship use of never is guaranteeing that a switch over a union handles every case. Assigning the narrowed value to a never variable in the default branch turns a missed case into a compile error.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "rect"; w: number; h: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
case "rect": return shape.w * shape.h;
default: {
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${_exhaustive}`);
}
}
}
If every kind is handled, shape narrows to never in default, so the assignment compiles. The moment someone adds a new variant — say { kind: "triangle" } — shape is no longer never, and the assignment fails to compile, pointing you straight at the unhandled case.
// After adding "triangle" to Shape without a case:
// ❌ Error: Type '{ kind: "triangle"; ... }' is not assignable to type 'never'.
const _exhaustive: never = shape;
This pattern makes unions self-enforcing. See Exhaustiveness Checking and Discriminated Unions for the broader technique.
never in conditional and mapped types
In type-level programming, never acts as a filter: members that distribute to never are removed from a union, and it marks impossible branches in conditional types.
// Remove a member from a union (this is how `Exclude` works)
type WithoutNull<T> = T extends null | undefined ? never : T;
type A = WithoutNull<string | null>; // string
// Mapped-type key filtering: keep only string-valued keys
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
type Keys = StringKeys<{ id: number; name: string }>; // "name"
Because never vanishes from unions, WithoutNull drops null, and the mapped type collapses non-matching keys to never, leaving only "name". This filtering behavior underpins many built-in utility types. Learn more in Conditional Types.
How never compares
never is best understood next to the two other “special” types. The table contrasts assignability and typical use.
| Aspect | any | unknown | never |
|---|---|---|---|
| Position | Disables checks | Top type | Bottom type |
| Assignable to it | Anything | Anything | Nothing (but never) |
| Assignable from it | Anything | unknown / any only | Everything |
| Has values | All | All | None |
| Type safety | None | Full | Full |
| Typical use | Avoid | Untrusted input | Impossible states, exhaustiveness |
Prefer the
const _exhaustive: never = valuepattern over a runtime-onlydefaultthrow. It moves the guarantee to compile time, so forgetting a union case becomes a build failure instead of a production bug.
Best Practices
- Annotate functions that always throw or loop as returning
never. - Add a
neverexhaustiveness check in thedefaultof everyswitchover a union. - Treat a
nevercompile error as a reminder to handle a newly added union case. - Use
neverto filter members in conditional types (this is howExcludeworks). - Don’t confuse
never(never returns) withvoid(returns nothing useful). - Pair
neverchecks with discriminated unions for safe modeling. - If a value unexpectedly narrows to
never, suspect contradictory or over-narrowed types.