Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

The never type represents the bottom type in TypeScript’s type system. It denotes a state that logically cannot exist, a value that will never occur, or an empty set of values. Because it is an empty set, no value can ever be assigned to a variable of type never at runtime.

Type System Mechanics and Assignability

As the bottom type, never sits at the very base of the type hierarchy. This dictates strict rules regarding assignability:
  1. never is assignable to every type. Because an empty set is a subset of every set, a never value can be safely assigned to any other type.
  2. No type is assignable to never. You cannot assign any value to a never type, except another never. Even the top type any cannot be assigned to never.
declare const neverValue: never;
declare const anyValue: any;

// Valid: never is assignable to all types
const a: string = neverValue;
const b: number = neverValue;
const c: { foo: string } = neverValue;

// Invalid: nothing is assignable to never
let d: never;
d = "hello"; // Error: Type 'string' is not assignable to type 'never'
d = null;    // Error: Type 'null' is not assignable to type 'never'
d = anyValue; // Error: Type 'any' is not assignable to type 'never'

Behavior in Type Operations

When evaluated within algebraic data types, never behaves according to standard set theory rules for empty sets. In Union Types (|) never acts as the identity element in a union. Adding an empty set to a union does not change the possible values, so the TypeScript compiler completely absorbs and removes never from the resulting type.
type StringOrNever = string | never; 
// Resolves to: string

type ComplexUnion = string | number | boolean | never; 
// Resolves to: string | number | boolean
In Intersection Types (&) never acts as the absorbing element in an intersection. The intersection of any set with an empty set is always an empty set. Furthermore, intersecting mutually exclusive types inherently produces an empty set, which TypeScript resolves to never.
type StringAndNever = string & never; 
// Resolves to: never

// Mutually exclusive types implicitly resolve to never
type StringAndNumber = string & number; 
// Resolves to: never

Function Return Inference

TypeScript automatically infers the never type as the return type for function expressions and arrow functions that possess an unreachable end point. This occurs when a function:
  1. Throws an exception, abruptly terminating execution.
  2. Contains an infinite loop, preventing it from ever returning control to the caller.
  3. Returns the result of another function that returns never.
(Note: Standard function declarations, e.g., function throwError() {}, historically and currently infer void rather than never in these scenarios unless explicitly annotated).
// Inferred return type: never
const throwError = (message: string) => {
    throw new Error(message);
};

// Inferred return type: never
const infiniteLoop = () => {
    while (true) {
        // execution trapped indefinitely
    }
};

// Inferred return type: never
const terminate = () => throwError("Fatal exception");
If a function has a reachable end point (even if it implicitly returns undefined), its return type is void, not never. void means a function returns nothing of value, whereas never means the function never successfully finishes executing.

Control Flow Analysis and Exhaustiveness Checking

TypeScript’s control flow analysis uses never to represent unreachable code paths. When evaluating a union type through conditional statements (like switch or if/else), TypeScript narrows the type based on the branches taken. If all possible constituents of the union have been exhausted by type guards, the remaining type narrows to never. This mechanic enforces compile-time exhaustiveness checking:
type Shape = { kind: "circle" } | { kind: "square" };

function getArea(shape: Shape) {
    switch (shape.kind) {
        case "circle":
            return Math.PI * 2;
        case "square":
            return 4;
        default:
            // Control flow analysis narrows 'shape' to 'never' here
            // because all possible union members were handled.
            const exhaustiveCheck: never = shape;
            return exhaustiveCheck;
    }
}
If the Shape union is later expanded (e.g., adding { kind: "triangle" }) but the switch statement is not updated, the default branch will infer shape as the unhandled type ({ kind: "triangle" }). The assignment to exhaustiveCheck will then trigger a compile-time error because a { kind: "triangle" } is not assignable to never, ensuring the developer handles all possible cases.
Master TypeScript with Deep Grasping Methodology!Learn More