What Is the Purpose of the never Type in TypeScript?

The never type in TypeScript represents values that can never occur, serving as the foundational building block for advanced type safety, unreachable code elimination, and exhaustive checks. Unlike void, which indicates that a function finishes executing without returning a meaningful value, never explicitly signals that execution will either throw an unhandled exception or fail to terminate entirely. Understanding when and how to leverage never enables developers to write resilient, future-proof code that allows the TypeScript compiler to catch logical omissions before they reach production.

Representing Impossible Values and Non-Terminating Functions

At its core, never acts as an indicator of absolute non-existence. When a function's return type is inferred or declared as never, it informs the compiler that the function will never return normally to its caller.

This occurs in two primary execution scenarios:

  1. Uncaught exceptions: Functions that unconditionally throw an error halt the normal flow of execution, meaning they never yield a return value.
  2. Infinite loops: Background workers, event-driven loops, or processes designed never to break their cycle will never produce a termination value.
function throwFatalError(message: string): never {
  throw new Error(`Fatal: ${message}`);
}

function runInfiniteEventLoop(): never {
  while (true) {
    // Process continuous events
  }
}

In these cases, assigning void would be technically imprecise. While void allows a caller to receive an implicit undefined, never makes it clear to both developers and the compiler that any subsequent statements following the function call are logically unreachable.

Exhaustive Pattern Matching and Union Discrimination

The most widespread practical pattern involving never is ensuring exhaustive checks across discriminated unions. When handling domain models with strict variants, such as state machines, API payloads, or UI actions, every possible case must be accounted for.

If you handle each variant inside a switch statement, narrowing eliminates each member from the union. The default branch should theoretically contain an impossible state. By assigning that remaining state to a variable typed as never, the compiler raises an error if an unhandled member remains:

type Shape = 
  | { kind: "circle"; radius: number }
  | { kind: "square"; sideLength: number }
  | { kind: "triangle"; base: number; height: number };

function calculateArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    case "triangle":
      return 0.5 * shape.base * shape.height;
    default: {
      // If a new shape is added to the union and omitted here,
      // TypeScript triggers a type error at compile time.
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
    }
  }
}

If an engineer introduces a { kind: "rectangle"; width: number; height: number } variant to the Shape union in the future, the code fails to compile immediately until the corresponding case "rectangle" is handled.

Bottom Type Mechanics in the Type System

In type theory, never functions as the bottom type, occupying the lowest position in the subtype hierarchy. This positioning gives it unique mathematical properties:

  • Subtype of everything: never is assignable to any other type, including string, number, or custom interfaces.
  • Nothing is assignable to it: No value or type can be assigned to never, except for never itself. Even any cannot be assigned to never.
  • Zero in unions: When merged into a union type, never is eliminated automatically. For example, string | never simplifies directly to string.
  • Absorptive in intersections: When intersected with another type, never dominates the evaluation. For example, string & never simplifies directly to never.

These algebraic characteristics make never an indispensable utility when designing conditional types to prune, filter, or validate properties.

Filtering Unwanted Types with Conditional Types

TypeScript's distributive conditional types leverage never to filter members out of unions. Because never collapses out of unions, mapping an unwanted branch to never removes it cleanly from the resulting type.

This mechanism powers standard TypeScript utility types like Exclude:

type Exclude = T extends U ? never : T;

// Evaluating the exclusion:
type AvailableRoles = "admin" | "editor" | "guest";
type ElevatedRoles = Exclude; 
// Resolves to: "admin" | "editor"

The evaluation evaluates each branch of the union individually:

  • "admin" extends "guest" evaluates to "admin"
  • "editor" extends "guest" evaluates to "editor"
  • "guest" extends "guest" evaluates to never

When reconstituted into a union, "admin" | "editor" | never resolves cleanly to "admin" | "editor".

Developers also use this technique to strip methods or non-serializable properties from data transfer objects:

type NonFunctionPropertyNames = {
  [K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];

interface UserProfile {
  id: string;
  name: string;
  save(): void;
}

type UserDataKeys = NonFunctionPropertyNames;
// Resolves to: "id" | "name"

Distinguishing never from void, null, and unknown

Confusion often arises between never and other non-value or universal types in TypeScript:

  • void vs never: A function returning void completes its execution block and returns undefined. A function returning never never reaches the end of its execution block.
  • null and undefined vs never: Both null and undefined are distinct runtime values that can be assigned, inspected, and held in memory. never is an abstract type that possesses no runtime representation or possible value.
  • unknown vs never: unknown is the top type, meaning everything is assignable to it, while never is the bottom type, meaning nothing can be assigned to it. They represent opposite extremes of TypeScript's type hierarchy.

By using never deliberately, developers transition from defensive runtime validations to structural, compile-time invariants across applications.