Menu

TypeScript never Type: Exhaustive Checks and never vs void

never is the type with no values. It is the return type of functions that never finish, the type left over when narrowing has ruled out every case, and the tool behind exhaustive switch checks. Learn where it comes from and how it differs from void.

This page includes runnable editors - edit, run, and see output instantly.

never is the type with no values. A function whose return type is never never returns normally: it throws or runs forever. A variable of type never marks code that cannot run, which is what makes exhaustive checks possible.

return fail(...) compiles in a function that returns number because never is assignable to every type: a value of type never can never actually arrive.

Functions That Never Return

Two kinds of function never reach their end: one that always throws, and one with an infinite loop. The compiler checks the claim. A function annotated never whose end is reachable is error TS2534, A function returning 'never' cannot have a reachable end point.

function fail(message: string): never {
  throw new Error(message);
}

function runForever(): never {
  while (true) {
    // poll, serve requests...
  }
}

Inference differs by syntax. A function declaration that only throws is inferred as returning void, while an arrow function or function expression that only throws is inferred as never:

function f1() { throw new Error("x"); }       // () => void
const f2 = () => { throw new Error("x"); };   // () => never

Narrowing only treats a call as a dead end when the called name has an explicit type that returns never: a function declaration annotated : never, like fail above, or a variable with a type annotation, const fail: (m: string) => never = (m) => { throw new Error(m); }. An inferred never does not count, and neither does const fail = (m: string): never => ..., where only the arrow is annotated and the variable is not.

never vs void

voidnever
Function finishesyesno (throws or loops forever)
Value at runtimeundefinednone: the call never produces one
Code after the callreachableunreachable
Assignable to other typesonly to void, unknown, anyto every type
Typical usecallbacks, event handlers, functions with side effectsfail(), assertNever(), infinite loops

The practical difference shows in narrowing. After if (!user) fail("no user"), the compiler knows user is defined on the next line only if fail returns never. With a void return it assumes execution can continue.

Exhaustive Checks with never

Each case of a switch over a union narrows the value. When every member has been handled, what is left in default is never. Assigning it to a never variable turns "I handled every case" into something the compiler verifies:

Now add a third member to the union without adding a case:

index.ts(18,26): error TS2345: Argument of type '{ kind: "triangle"; base: number; height: number; }' is not assignable to parameter of type 'never'.

The error names the member you forgot. Add case "triangle": return (shape.base * shape.height) / 2; and it compiles again. The assertNever helper is the reusable form of the same check, and its throw still matters at runtime: data from JSON or an older client can contain a kind the types say is impossible. This pattern is the backbone of discriminated unions.

Narrowing Down to never

The same thing happens with any narrowing, not only switch. Once every possibility is ruled out, the variable has type never:

If you later widen the parameter to string | number | boolean | bigint, the const nothing: never = x line becomes an error, pointing at the function that needs updating.

never Disappears in Unions

never is the empty set of values, so adding it to a union changes nothing: string | never is just string. That is how conditional types filter unions. A branch that returns never removes that member:

The built-in Exclude<T, U> and Extract<T, U> work exactly this way. In an intersection it is the opposite: string & never is never.

Impossible Types Become never

An intersection that no value can satisfy reduces to never:

type A = string & number;                   // never
type B = { kind: "a" } & { kind: "b" };     // never

Reading a property of a B value reports the reason: Property 'kind' does not exist on type 'never'. The intersection 'B' was reduced to 'never' because property 'kind' has conflicting types in some constituents. When a type you built turns out to be never, look for two parts that contradict each other.

never, unknown and any

TypeValues it holdsAssignable toAccepts
unknownevery valueonly unknown and anyeverything
anyevery valueeverything except nevereverything
neverno valueseverythingonly never

unknown is the top of the type hierarchy and never is the bottom. any is not part of the hierarchy at all: it switches the checks off.

Frequently Asked Questions

What is the never type in TypeScript?

never is the type that has no values. Nothing can be assigned to it (except another never), and it is assignable to every type. It shows up as the return type of functions that always throw or loop forever, as the type of a variable after narrowing has ruled out every possibility, and as the result of impossible types such as string & number.

What is the difference between never and void?

A function returning void finishes normally; it just does not return a useful value (at runtime it returns undefined). A function returning never does not finish at all: it throws or runs forever. Code after a call to a never function is unreachable, and TypeScript treats it that way when narrowing.

How do you do an exhaustive check in TypeScript?

In the default branch of a switch over a union, assign the value to a variable of type never, or pass it to a function assertNever(value: never): never that throws. If every case is handled, the value is never there and the code compiles. If a case is missing, the compiler reports that the missing member is not assignable to never.

Why is my type never?

Usually because TypeScript narrowed away every option (for example after checking typeof x === "string" and typeof x === "number" on a string | number), or because an intersection is impossible: string & number, or two object types whose shared property has conflicting literal types. Hover the type in the editor to see which step produced it.

What does "is not assignable to type never" mean?

The code tried to put a real value where only never is allowed. In an exhaustive check it means a union member was not handled. Elsewhere it often means an array was inferred as never[] or an intersection collapsed to never; add an annotation or fix the conflicting types.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED