The never type represents values that never occur. Here's how to use it effectively.
Understanding never#
1// never is the bottom type
2// No value can be assigned to never
3let x: never;
4
5// x = 1; // Error
6// x = 'string'; // Error
7// x = null; // Error
8// x = undefined; // Error
9
10// never is a subtype of all types
11const n: never = undefined as never;
12const str: string = n; // OK - never assignable to anything
13const num: number = n; // OK
14const bool: boolean = n; // OK
15
16// But nothing is assignable to never (except never)
17// const n2: never = 'hello'; // ErrorFunctions That Never Return#
1// Function that throws
2function throwError(message: string): never {
3 throw new Error(message);
4}
5
6// Function with infinite loop
7function infiniteLoop(): never {
8 while (true) {
9 // Never exits
10 }
11}
12
13// Comparison with void
14function logMessage(msg: string): void {
15 console.log(msg);
16 // Returns undefined implicitly
17}
18
19function fail(msg: string): never {
20 throw new Error(msg);
21 // Never returns anything
22}
23
24// Usage in call chains
25function processData(data: unknown): string {
26 if (typeof data !== 'string') {
27 throwError('Expected string'); // never type - code below unreachable
28 }
29 return data.toUpperCase(); // TypeScript knows data is string
30}Exhaustive Checking#
1// Ensure all cases handled
2type Shape = 'circle' | 'square' | 'triangle';
3
4function getArea(shape: Shape): number {
5 switch (shape) {
6 case 'circle':
7 return Math.PI * 10 ** 2;
8 case 'square':
9 return 10 * 10;
10 case 'triangle':
11 return (10 * 10) / 2;
12 default:
13 // If all cases handled, shape is never
14 const _exhaustive: never = shape;
15 return _exhaustive;
16 }
17}
18
19// Add new shape - TypeScript catches missing case
20type ShapeV2 = 'circle' | 'square' | 'triangle' | 'rectangle';
21
22function getAreaV2(shape: ShapeV2): number {
23 switch (shape) {
24 case 'circle':
25 return Math.PI * 10 ** 2;
26 case 'square':
27 return 10 * 10;
28 case 'triangle':
29 return (10 * 10) / 2;
30 // Missing 'rectangle' case!
31 default:
32 const _exhaustive: never = shape;
33 // Error: Type 'string' is not assignable to type 'never'
34 return _exhaustive;
35 }
36}
37
38// Helper function
39function assertNever(value: never, message?: string): never {
40 throw new Error(message || `Unexpected value: ${value}`);
41}
42
43function getAreaWithHelper(shape: Shape): number {
44 switch (shape) {
45 case 'circle':
46 return Math.PI * 10 ** 2;
47 case 'square':
48 return 10 * 10;
49 case 'triangle':
50 return (10 * 10) / 2;
51 default:
52 return assertNever(shape, `Unknown shape: ${shape}`);
53 }
54}Type Narrowing#
1// never appears after exhaustive narrowing
2function process(value: string | number) {
3 if (typeof value === 'string') {
4 console.log(value.toUpperCase());
5 } else if (typeof value === 'number') {
6 console.log(value.toFixed(2));
7 } else {
8 // value is never here - all cases handled
9 const _check: never = value;
10 }
11}
12
13// With discriminated unions
14type Result =
15 | { type: 'success'; data: string }
16 | { type: 'error'; error: Error };
17
18function handleResult(result: Result) {
19 switch (result.type) {
20 case 'success':
21 console.log(result.data);
22 break;
23 case 'error':
24 console.error(result.error);
25 break;
26 default:
27 const _exhaustive: never = result;
28 }
29}Conditional Types#
1// never in conditional types
2type NonNullable<T> = T extends null | undefined ? never : T;
3
4type A = NonNullable<string | null>; // string
5type B = NonNullable<undefined | number>; // number
6type C = NonNullable<null>; // never
7
8// Filter from union
9type Filter<T, U> = T extends U ? never : T;
10
11type Numbers = Filter<string | number | boolean, string>;
12// number | boolean
13
14// Exclude (built-in)
15type Exclude<T, U> = T extends U ? never : T;
16
17type WithoutString = Exclude<string | number | boolean, string>;
18// number | boolean
19
20// Extract (built-in)
21type Extract<T, U> = T extends U ? T : never;
22
23type OnlyStrings = Extract<string | number | boolean, string>;
24// string
25
26// never in unions disappears
27type Union = string | never | number;
28// string | numberImpossible States#
1// Represent impossible combinations
2type LoadingState = {
3 status: 'loading';
4 data?: never; // Can't have data while loading
5 error?: never; // Can't have error while loading
6};
7
8type SuccessState = {
9 status: 'success';
10 data: string;
11 error?: never; // Can't have error on success
12};
13
14type ErrorState = {
15 status: 'error';
16 data?: never; // Can't have data on error
17 error: Error;
18};
19
20type State = LoadingState | SuccessState | ErrorState;
21
22// TypeScript enforces valid states
23const loading: State = { status: 'loading' }; // OK
24const success: State = { status: 'success', data: 'Hello' }; // OK
25const error: State = { status: 'error', error: new Error() }; // OK
26
27// Invalid states caught at compile time
28// const invalid: State = {
29// status: 'success',
30// data: 'Hello',
31// error: new Error() // Error: Type 'Error' is not assignable to type 'undefined'
32// };Function Overloads#
1// never helps with overload resolution
2function processValue(value: string): string;
3function processValue(value: number): number;
4function processValue(value: string | number): string | number {
5 if (typeof value === 'string') {
6 return value.toUpperCase();
7 }
8 return value * 2;
9}
10
11// Without proper overloads, return would include never
12type Result = ReturnType<typeof processValue>;
13// string | number (never filtered out)Generic Constraints#
1// Use never for impossible generics
2type Keys<T> = T extends object ? keyof T : never;
3
4type StringKeys = Keys<{ a: 1; b: 2 }>; // 'a' | 'b'
5type NoKeys = Keys<string>; // never
6
7// Require at least one property
8type RequireAtLeastOne<T> = {
9 [K in keyof T]: Pick<T, K>;
10}[keyof T];
11
12// If no properties, becomes never
13type Empty = RequireAtLeastOne<{}>; // never
14
15// Mutual exclusion
16type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
17
18type User = {
19 id: number;
20 name: string;
21 email: string;
22};
23
24type UserWithoutId = Without<User, 'id'>;
25// { name: string; email: string; }Error Handling Patterns#
1// Type-safe error handling
2type Success<T> = { success: true; value: T };
3type Failure<E> = { success: false; error: E };
4type Result<T, E = Error> = Success<T> | Failure<E>;
5
6function divide(a: number, b: number): Result<number, string> {
7 if (b === 0) {
8 return { success: false, error: 'Division by zero' };
9 }
10 return { success: true, value: a / b };
11}
12
13function handleResult<T, E>(
14 result: Result<T, E>,
15 onSuccess: (value: T) => void,
16 onFailure: (error: E) => void
17): void {
18 if (result.success) {
19 onSuccess(result.value);
20 } else {
21 onFailure(result.error);
22 }
23}
24
25// Never-returning error handler
26function panic(error: string): never {
27 console.error(error);
28 process.exit(1);
29}
30
31function unwrap<T>(result: Result<T>): T {
32 if (result.success) {
33 return result.value;
34 }
35 return panic(`Unwrap failed: ${result.error}`);
36}Best Practices#
Use never For:
✓ Exhaustive switch checks
✓ Functions that throw/loop forever
✓ Filtering union types
✓ Representing impossible states
Exhaustive Checking:
✓ Add default case with never
✓ Create assertNever helper
✓ Catch missing cases at compile time
✓ Use discriminated unions
Type Design:
✓ Use never for impossible properties
✓ Filter with conditional types
✓ Validate completeness
✓ Document never in APIs
Avoid:
✗ Assigning to never intentionally
✗ Ignoring never in results
✗ Over-complicating with never
✗ Forgetting exhaustive checks
Conclusion#
The never type represents impossibility in TypeScript's type system. Use it for exhaustive checks, functions that don't return, conditional type filtering, and representing impossible states. It's essential for type-safe switch statements and catching missing cases at compile time.