This error occurs when TypeScript's type narrowing determines that a code path is unreachable (type 'never'), and you're attempting to access properties on it. The fix involves proper type guards and exhaustiveness checking.
The 'never' type in TypeScript represents a type that has no values—an impossible state. When you see this error, it means TypeScript's control flow analysis has determined that your code should never reach that point. This commonly happens when: 1. You have a union type and forget to handle all possible cases 2. You have conditional type guards that don't cover all branches 3. TypeScript's type narrowing eliminates all possibilities before a certain line Accessing a property on 'never' is an error because logically, if a value can never exist, you can't access its properties. TypeScript is telling you something is wrong with your type logic, not your runtime code.
First, locate the variable with type 'never'. Check what union type led to this:
type Status = 'pending' | 'success' | 'error';
function handleStatus(status: Status) {
if (status === 'pending') {
// handle pending
} else if (status === 'success') {
// handle success
}
// At this point, TypeScript knows status is 'never' if not all cases are handled
}Use your IDE's hover feature to see what type TypeScript infers at each point.
Create an exhaustiveness check function that catches unhandled cases:
function assertNever(value: never): never {
throw new Error(`Unhandled value: ${value}`);
}
type Status = 'pending' | 'success' | 'error';
function handleStatus(status: Status) {
switch (status) {
case 'pending':
console.log('Pending...');
break;
case 'success':
console.log('Success!');
break;
case 'error':
console.log('Error occurred');
break;
default:
assertNever(status); // TypeScript error if cases missing
}
}If you add a new status value to the union and forget to handle it in the switch, TypeScript will error at the assertNever call.
With if/else chains, ensure every case is covered:
type Response = { type: 'success'; data: string } | { type: 'error'; error: Error };
function handleResponse(response: Response) {
if (response.type === 'success') {
console.log(response.data);
} else if (response.type === 'error') {
console.log(response.error);
}
// All cases covered, no 'never' type here
}If you remove the else if block or don't handle all discriminator values, you'll get the 'never' error.
For complex type guards, define explicit predicates:
interface Dog { type: 'dog'; bark(): void }
interface Cat { type: 'cat'; meow(): void }
type Animal = Dog | Cat;
function isDog(animal: Animal): animal is Dog {
return animal.type === 'dog';
}
function isCat(animal: Animal): animal is Cat {
return animal.type === 'cat';
}
function interact(animal: Animal) {
if (isDog(animal)) {
animal.bark();
} else if (isCat(animal)) {
animal.meow();
}
// TypeScript knows all cases are covered
}In tsconfig.json, ensure strict mode is enabled:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}Strict mode catches more type errors, including incomplete union handling.
### Understanding 'never' in Type Narrowing
The 'never' type is the bottom type—assignable to every type, but no value is assignable to it. When TypeScript's control flow analysis exhausts all possible cases, the remaining type becomes 'never'.
type Literal = 'a' | 'b' | 'c';
function example(x: Literal) {
if (x === 'a') {
// x is 'a'
} else if (x === 'b') {
// x is 'b'
} else if (x === 'c') {
// x is 'c'
} else {
// x is 'never' — impossible to reach
}
}### Single-Value Union Edge Case
TypeScript has a known limitation: exhaustiveness checks work best with unions of 2+ members. Single-value unions sometimes don't trigger the 'never' type properly due to implicit any coercion.
Workaround: use a union of at least 2 types or force strict narrowing:
// Avoid:
type Single = 'only-value';
// Better:
type Single = 'only-value' | never;### Discriminated Unions (Recommended Pattern)
For complex types, use discriminant properties:
type Result<T> =
| { kind: 'success'; value: T }
| { kind: 'error'; error: string };
function process<T>(result: Result<T>) {
switch (result.kind) {
case 'success':
return result.value;
case 'error':
throw new Error(result.error);
default:
const _exhaustive: never = result;
return _exhaustive;
}
}TypeScript uses the kind field to narrow the type, eliminating 'never' errors when properly handled.
### Async/Promise Edge Cases
'never' can appear with promise rejection handling:
async function fetchUser(id: string): Promise<never> {
// This function never resolves or returns—it always throws or runs forever
throw new Error('Not found');
}Use Promise types for functions that might resolve.
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 narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript