This error occurs when TypeScript's type narrowing eliminates all possible types from a union, leaving the impossible "never" type. It typically happens with typeof checks that exclude all valid type options.
This error appears when TypeScript's control flow analysis determines that a variable can never have a valid type at a specific point in your code. This happens most commonly when using `typeof` type guards that eliminate every possible type from a union type. TypeScript uses the `never` type to represent values that can never occur. When you apply type guards (like `typeof x === "string"`) to narrow down union types, and all possible types have been eliminated through your checks, TypeScript assigns the `never` type to indicate an unreachable code path. The issue often arises from overly restrictive type guards, incorrect assumptions about what types a variable can hold, or bugs in TypeScript's type narrowing algorithm itself (particularly with complex object property narrowing or negated typeof checks).
First, locate exactly where TypeScript is inferring the never type. Hover over the variable in your IDE or check the compiler error message:
function process(value: string | number) {
if (typeof value === 'string') {
// value is string here
} else if (typeof value === 'number') {
// value is number here
} else {
// value is never here - all cases handled
console.log(value); // Error: value is never
}
}The never type indicates that this code path should be unreachable given the type constraints.
Sometimes never is actually desired for exhaustive type checking. This is a common pattern to ensure all union cases are handled:
type Action = { type: 'add' } | { type: 'remove' };
function handleAction(action: Action) {
switch (action.type) {
case 'add':
return 'Adding';
case 'remove':
return 'Removing';
default:
// This should be never - proves exhaustiveness
const _exhaustive: never = action;
throw new Error(`Unhandled action: ${_exhaustive}`);
}
}If this is your use case, the never type is working correctly.
Check that your type annotation actually includes all the types you expect. A mismatch here causes unexpected narrowing:
// Problem: type doesn't include all expected values
let value: string | number = getValue();
if (typeof value === 'boolean') {
// value is never - boolean not in union
console.log(value);
}
// Solution: Include all possible types
let value: string | number | boolean = getValue();
if (typeof value === 'boolean') {
// value is boolean - works correctly
console.log(value);
}Negated typeof checks can be problematic. Instead of checking what something is NOT, check what it IS:
// Problem: negated check narrows incorrectly
function process(value: string | number | object) {
if (typeof value !== 'object') {
// May narrow to never in some TS versions
}
}
// Solution: Use positive type guards
function process(value: string | number | object) {
if (typeof value === 'string' || typeof value === 'number') {
// value is string | number
console.log(value);
} else {
// value is object
console.log(value);
}
}For complex type checking, custom type predicates give you more control than typeof:
interface Cat { meow(): void }
interface Dog { bark(): void }
type Animal = Cat | Dog;
// Type predicate function
function isCat(animal: Animal): animal is Cat {
return 'meow' in animal;
}
function handleAnimal(animal: Animal) {
if (isCat(animal)) {
animal.meow(); // Correctly narrowed to Cat
} else {
animal.bark(); // Correctly narrowed to Dog
}
}If you encounter a never type that shouldn't exist (possibly a TypeScript bug), you can work around it temporarily:
// Workaround: Type assertion
function process(value: string | number) {
if (typeof value === 'string') {
// handle string
} else {
// TypeScript thinks this is never incorrectly
const numValue = value as number;
console.log(numValue);
}
}
// Better: Widen the type explicitly
function process(value: string | number) {
const widened: string | number = value;
if (typeof widened === 'string') {
// handle string
} else {
console.log(widened); // number, not never
}
}Understanding never in the type system:
The never type is the bottom type in TypeScript's type hierarchy. It's assignable to every type, but no type is assignable to never (except never itself). This makes it perfect for representing impossible states and unreachable code.
Known TypeScript issues:
Several GitHub issues document cases where typeof narrowing produces unexpected never types:
- Issue #27103: typeof narrowing with object types can incorrectly produce never
- Issue #32585: Property narrowing in loops can produce weird never types
- Issue #41270: Union types in let declarations can be erroneously narrowed to never
- Issue #54032: Creating never from void functions using typeof
These are typically edge cases involving complex type combinations, property access in narrowed types, or interactions between control flow analysis and type guards.
Discriminated unions vs typeof:
For complex object types, discriminated unions are more reliable than typeof checks:
// Instead of typeof checks
type Response =
| { success: true; data: string }
| { success: false; error: string };
function handle(response: Response) {
if (response.success) {
console.log(response.data); // Correctly narrowed
} else {
console.log(response.error); // Correctly narrowed
}
}StrictNullChecks impact:
With strictNullChecks enabled, the never type appears more frequently because TypeScript is more aggressive about eliminating impossible type combinations. This is generally desirable as it catches more bugs at compile time.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript
Type 'X' is not assignable to inferred conditional type
How to fix "Type not assignable to inferred conditional" in TypeScript