Mastering the Never Type in TypeScript
TypeScript’s never type is one of the language’s most misunderstood but most powerful tools. It helps you enforce exhaustive checks, catch impossible states, eliminate unreachable logic, and build advanced type utilities.
This guide explains what never is, when to use it, and how to combine it with TypeScript’s type system to write safer, more predictable code.
What Is the never Type?
The never type represents values that never occur. You’ll see it in:
- functions that throw errors
- functions that never return
- unreachable code
- advanced type expressions
Example:
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}Both functions never produce a value, so their return type is never.
When Should You Use never?
1. Exhaustive Checking
Prevent missing cases in switch statements.
type Shape = 'circle' | 'square';
function area(shape: Shape): number {
switch (shape) {
case 'circle':
return Math.PI * 10 * 10;
case 'square':
return 10 * 10;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}If a new union member is added, TypeScript warns you.
2. Handling Unreachable Code
Ensure all type cases are handled.
function handleInput(input: string | number) {
if (typeof input === 'string') {
console.log('string:', input);
} else if (typeof input === 'number') {
console.log('number:', input);
} else {
const _: never = input; // compile error if input is unexpected
}
}3. Functions That Must Not Return
function assertIsDefined<T>(value: T | undefined): T {
if (value === undefined) {
throw new Error('Value is undefined');
}
return value;
}This pattern is common in error-throwing assertion functions.
Advanced Examples
Example 1 — never as a Subtype
type TValue = string | (never extends string ? true : false); // trueBecause never is a subtype of every type.
Example 2 — Eliminating Impossible Types
type GenericWithNever<T> = T extends string ? T : never;
const x: GenericWithNever<string> = 'ok';
// @ts-expect-error
const y: GenericWithNever<number> = 'nope';If T is not a string, the result becomes never, removing invalid types.
Real-World Example: Generating Path-Based Keys
Given a nested object:
const messages = {
defaultPrompt: { ok: 'Ok', cancel: 'Cancel' },
defaultAction: {
file: { rm: 'delete file', create: 'create file' },
directory: { rm: 'delete directory', create: 'make directory' }
},
title1: 'default title'
} as const;Goal: allow keys like:
defaultAction.file.rm
defaultPrompt.ok
title1Solution: Recursive Key Extraction
type TExtractAllKeys<O, K extends keyof O = keyof O> =
K extends string
? O[K] extends string
? K
: `${K}.${TExtractAllKeys<O[K]>}`
: never;Usage:
const getMessageByKey = (key: TExtractAllKeys<typeof messages>): string =>
eval(`messages.${key}`);never eliminates invalid cases automatically.
Best Practices for Using never
- Use for exhaustive checking to avoid missing branches.
- Clarify intent when a function should never return.
- Combine with type guards for total safety.
- Use in utility types to filter out unwanted members.
- Avoid overuse—keep code readable.
Conclusion
The never type is a powerful ally when writing robust TypeScript. Whether you’re validating exhaustive logic, cleaning up unreachable code, or building advanced type utilities, never ensures your code stays safe, predictable, and future-proof.
Mastering never means mastering TypeScript’s type system. With the patterns in this guide, you’re ready to use it like a pro.