Suggest an editImprove this articleRefine the answer for “Never type in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`never`** is the TypeScript bottom type - no value of this type can exist at runtime. ```typescript function fail(msg: string): never { throw new Error(msg); // never returns } type Status = 'ok' | 'error'; // const check: never = s in default catches missing cases ``` **Key:** `never` is for paths that do not complete (throw, infinite loop, impossible branch), not for functions that simply return nothing (use `void` for those).Shown above the full answer for quick recall.Answer (EN)Image**`never`** is the TypeScript bottom type: no value of this type can exist at runtime. ## Theory ### TL;DR - `never` means "this code path is impossible" - the compiler treats it as a dead end - Analogy: a one-way road that leads to a wall. No value comes out the other side. - `void` = function finishes but returns nothing. `never` = function does not finish at all. - Put `const x: never = value` in a switch `default` to catch unhandled union members at compile time - `never` disappears inside unions: `string | never` equals `string` ### Quick example ```typescript // Throws instead of returning - annotated as never function fail(message: string): never { throw new Error(message); } // Exhaustive check on a discriminated union type Status = 'ok' | 'error' | 'pending'; function describe(s: Status): string { switch (s) { case 'ok': return 'All good'; case 'error': return 'Something failed'; case 'pending': return 'Still waiting'; default: const check: never = s; // compile error if Status gets a new member throw new Error('Unhandled: ' + check); } } ``` Add a new member to `Status` without updating the switch and the compiler errors on that `check` line. The bug is caught before the code ships. ### Key difference from void `void` says the function ran to completion and returned nothing useful. Returning `undefined` is perfectly fine. `never` says the function did not finish at all - it threw, looped forever, or hit a branch the compiler proved unreachable. Nothing escapes a `never` annotation. ### When to use - Error-throwing helpers: `function fail(msg: string): never { throw new Error(msg); }` - Exhaustive switch on a discriminated union: `const x: never = value` in the `default` case - Infinite polling or server loops that truly never return - Conditional type filters: `T extends never ? ... : ...` removes impossible types in generic utilities Do not reach for `never` to mean "empty" or "nothing." That job belongs to `void` or `undefined`. ### How the compiler handles this TypeScript's control flow analysis tracks every reachable path. After a `throw`, after an infinite loop, or once all union members have been narrowed away, TypeScript marks what remains as `never`. This is compile-time only. Node.js and V8 never see it - the type is erased like every other TypeScript annotation before the code runs. Once you work in a codebase where union types change often, the `never` exhaustive check becomes the most reliable way to find every switch that needs updating when a new variant is added. ### Common mistakes **Mistake 1: annotating an empty array as `never[]`** ```typescript const items: never[] = []; // wrong - nothing can ever be added const items: string[] = []; // right ``` A `never[]` array rejects every push and initialization value. TypeScript refuses any element because the element type is impossible. **Mistake 2: returning from a `never` function** ```typescript function crash(): never { throw new Error('down'); return 'oops'; // compile error - unreachable and contradicts never } ``` Every code path in a `never`-annotated function must throw or loop. A `return` statement breaks that contract. **Mistake 3: putting `never` in a union expecting it to do something** ```typescript type ID = string | number | never; // same as: string | number ``` `never` is absorbed by any union. Use `Exclude<T, U>` to remove a specific member instead. **Mistake 4: using `never` where `void` belongs** ```typescript function log(msg: string): never { // compile error - function returns normally console.log(msg); } function log(msg: string): void { // correct console.log(msg); } ``` A function that logs something and finishes normally is `void`, not `never`. ### Real-world usage - **React**: exhaustive prop checks in polymorphic components - `const check: never = variant` in `default` - **Zod**: parse failures call a `never`-returning function to signal that execution stops there - **Redux Toolkit**: `PayloadAction<never>` for actions that carry no payload - **tRPC**: procedure failures are typed as `never` so callers cannot access a result that does not exist ### Follow-up questions **Q:** What is the difference between `never` and `void`? **A:** `void` allows `undefined` and means the function ran to its end. `never` means it did not. No value is assignable to `never`, not even `undefined`. **Q:** When does TypeScript infer `never` on its own? **A:** After a `throw`, after an infinite loop, and once all members of a union have been narrowed away. When nothing is left, the type becomes `never`. **Q:** What happens to `never` inside a union? **A:** It disappears. `string | never` collapses to `string`. Writing `never` in a plain union type has no effect. **Q:** How does the exhaustive check pattern actually work? **A:** Assign the `default` branch value to a variable typed as `never`. If a new union member is added and not handled, TypeScript sees that variable is no longer `never` and raises a compile error before the code runs. **Q:** What is `asserts never` and when does it appear? **A:** It is a type predicate on assertion functions. A function typed as `(x: unknown): asserts x is never` tells TypeScript that after it returns, the value passed in is treated as impossible - effectively narrowing it out of all further checks. ## Examples ### Exhaustive variant check in a component ```typescript type AlertVariant = 'success' | 'error' | 'warning'; function getAlertClass(variant: AlertVariant): string { switch (variant) { case 'success': return 'bg-green-500'; case 'error': return 'bg-red-500'; case 'warning': return 'bg-yellow-500'; default: // Add 'info' to AlertVariant without updating here // and this line produces a compile error immediately const exhausted: never = variant; throw new Error(`Unhandled variant: ${exhausted}`); } } ``` When a teammate adds `'info'` to `AlertVariant` and forgets to update this function, the compiler flags the `default` branch right away. The bug surfaces at compile time, not in a user's browser. ### Error-throwing helper as a building block ```typescript function failWith(message: string): never { throw new Error(message); } function assertDefined<T>(value: T | null | undefined, field: string): T { if (value == null) { throw new Error(`${field} is required`); } return value; } // Because failWith returns never, TypeScript narrows token to string const token = user.token ?? failWith('Missing token'); // ^? string (not string | undefined) ``` The `never` return type on `failWith` tells TypeScript that the `??` right-hand side always terminates execution. So `token` is inferred as `string`, not `string | undefined`. Without the `never` annotation that narrowing does not happen.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.