ZetCode

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.

fail.ts
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.

never_void.ts
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.

exhaustive.ts
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.

missing_case.ts
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.

assert_never.ts
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.

never_union.ts
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.

intersection.ts
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.

filter_union.ts
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.

filter_keys.ts
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.

narrow_never.ts
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

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.