TypeScript never Type
last modified October 15, 2026
The never type represents values that can never occur.
No value can be assigned to it, not even undefined or
null. It is the type of functions that never return normally and
the type that remains when all possibilities have been ruled out. Its most
useful application is checking that every case of a union type is handled.
Functions that never return
A function that always throws an exception never returns a value.
Its return type is never.
function fail(message: string): never {
throw new Error(message);
}
function parseAge(text: string): number {
const age = Number(text);
if (Number.isNaN(age)) {
fail(`not a number: ${text}`);
}
return age;
}
console.log(parseAge("42"));
try {
parseAge("forty-two");
} catch (e) {
console.log(e instanceof Error ? e.message : e);
}
The fail function is annotated with
never. A function with an infinite loop and no
break, for example an event loop of a server, also has the
never return type.
$ npx tsx fail.ts 42 not a number: forty-two
never versus void
A void function returns normally, just without a useful
value. A never function does not return at all. The compiler uses
this difference in control flow analysis.
function logError(message: string): void {
console.log(`error: ${message}`);
}
function throwError(message: string): never {
throw new Error(message);
}
function getLength(text: string | null): number {
if (text === null) {
throwError("text is null");
}
// text is narrowed to string here
return text.length;
}
logError("disk full");
console.log(getLength("falcon"));
After the call to throwError, the compiler knows that
text cannot be null. If logError were
called instead, text.length would be an error, because the
function returns and execution continues.
$ npx tsx never_void.ts error: disk full 6
Exhaustive checks
When a switch statement handles every member of a
union, the variable has the never type in the
default branch. Assigning it to a never variable
makes the compiler verify that no case is missing.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "rectangle"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
case "rectangle":
return shape.width * shape.height;
default: {
const unreachable: never = shape;
return unreachable;
}
}
}
const shapes: Shape[] = [
{ kind: "circle", radius: 1 },
{ kind: "square", side: 3 },
{ kind: "rectangle", width: 2, height: 5 },
];
for (const shape of shapes) {
console.log(shape.kind, area(shape).toFixed(2));
}
The Shape type is a discriminated union; the
kind property tells the variants apart. In the
default branch, all three variants have been handled, so
shape has the never type and the assignment
compiles.
$ npx tsx exhaustive.ts circle 3.14 square 9.00 rectangle 10.00
A forgotten case
Now we add a new variant to the union and forget to handle it.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
default: {
const unreachable: never = shape;
return unreachable;
}
}
}
The compiler reports the error at the assignment and names the
variant that is not handled. Without the check, the function would silently
return undefined for triangles.
$ npx tsc --noEmit
missing_case.ts(13,19): error TS2322: Type '{ kind: "triangle"; base: number; height: number; }' is not assignable to type 'never'.
The assertNever helper
The check is often moved into a helper function, which also throws an error if an unexpected value arrives at runtime, for example from unvalidated JSON.
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${JSON.stringify(value)}`);
}
type Status = "active" | "suspended" | "deleted";
function label(status: Status): string {
switch (status) {
case "active":
return "Account is active";
case "suspended":
return "Account is suspended";
case "deleted":
return "Account was deleted";
default:
return assertNever(status);
}
}
console.log(label("active"));
console.log(label("deleted"));
const fromJson = JSON.parse('"archived"') as Status;
try {
console.log(label(fromJson));
} catch (e) {
console.log(e instanceof Error ? e.message : e);
}
The value "archived" is not a valid
Status, but the type assertion hides this from the compiler. At
runtime, it reaches the default branch and
assertNever throws a descriptive error.
$ npx tsx assert_never.ts Account is active Account was deleted Unexpected value: "archived"
never in union types
The never type is the empty set of values. In a union,
it disappears.
type A = string | never; type B = string | number | never; const a: A = "hello"; const b: B = 42; console.log(a, b);
The type A is just string and
B is string | number. This property is the basis of
type filtering, shown in the next sections.
$ npx tsx never_union.ts hello 42
Impossible intersections
An intersection of types that cannot overlap produces
never.
type Impossible = string & number; const value: Impossible = "text";
No value is both a string and a number, so the
Impossible type is never and nothing can be
assigned to it.
$ npx tsc --noEmit intersection.ts(3,7): error TS2322: Type '"text"' is not assignable to type 'never'.
Filtering with conditional types
A conditional type applied to a union is evaluated for each member
separately. Members mapped to never are removed from the
result.
type OnlyStrings<T> = T extends string ? T : never; type Mixed = "red" | "green" | 1 | 2 | true; type Colors = OnlyStrings<Mixed>; const colors: Colors[] = ["red", "green"]; console.log(colors);
The Colors type is "red" | "green". The
built-in Extract and Exclude utility types work the
same way.
$ npx tsx filter_union.ts [ 'red', 'green' ]
Filtering keys in mapped types
In a mapped type, remapping a key to never with the
as clause removes the property.
type User = {
id: number;
name: string;
email: string;
active: boolean;
};
type StringProps<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
const contact: StringProps<User> = {
name: "Alice",
email: "alice@example.com",
};
console.log(Object.keys(contact));
The StringProps<User> type contains only the
name and email properties; id and
active were mapped to never and dropped.
$ npx tsx filter_keys.ts [ 'name', 'email' ]
Narrowing to never
When all possibilities are excluded, the compiler narrows a variable
to never.
function check(value: string | number): string {
if (typeof value === "string") {
return "string";
}
if (typeof value === "number") {
return "number";
}
// value is never here
return value;
}
console.log(check("a"), check(1));
After both checks, value has the never
type. Returning it is allowed, because never is assignable to
every type.
$ npx tsx narrow_never.ts string number
Best Practices
- Annotate Throwing Functions: Give helpers that always throw the
neverreturn type, so the compiler can narrow after calls to them. - Check Unions Exhaustively: Add a
nevercheck to thedefaultbranch of switches over union types. - Throw on Unexpected Values: Use an
assertNeverfunction that also fails at runtime for invalid data. - Use never to Filter Types: Map unwanted union members or keys to
neverin conditional and mapped types. - Read never in Errors Carefully: An unexpected
neverin an error message usually means that a type was narrowed or intersected away completely.
Source
TypeScript Handbook: The never type
This tutorial covered the TypeScript never type. Its
most practical use is the exhaustive check, which turns a forgotten case into
a compile-time error.
Author
List all TypeScript tutorials.