Skip to content
TypeScript ts basic-types 4 min read

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.

Aspectanyunknownnever
PositionDisables checksTop typeBottom type
Assignable to itAnythingAnythingNothing (but never)
Assignable from itAnythingunknown / any onlyEverything
Has valuesAllAllNone
Type safetyNoneFullFull
Typical useAvoidUntrusted inputImpossible states, exhaustiveness

Prefer the const _exhaustive: never = value pattern over a runtime-only default throw. 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 never exhaustiveness check in the default of every switch over a union.
  • Treat a never compile error as a reminder to handle a newly added union case.
  • Use never to filter members in conditional types (this is how Exclude works).
  • Don’t confuse never (never returns) with void (returns nothing useful).
  • Pair never checks with discriminated unions for safe modeling.
  • If a value unexpectedly narrows to never, suspect contradictory or over-narrowed types.
Last updated June 29, 2026
Was this helpful?