This TypeScript error occurs when you try to access a property on a value that TypeScript has narrowed to type 'never'. It typically happens with incomplete type guards, missing union cases, or exhaustive switch statements. The fix involves proper type narrowing and exhaustiveness checking.
The 'never' type in TypeScript represents values that should never occur—an impossible or unreachable state. When you see "Property 'name' does not exist on type 'never'", TypeScript is telling you that: 1. **Type narrowing has eliminated all possible types**: Through conditional checks (if/else, switch), TypeScript has determined that a particular code path should be unreachable. 2. **You're accessing a property on an impossible value**: Since the type is 'never' (no possible values), you can't access properties like 'name' on it. 3. **There's a logical gap in your type handling**: This error usually indicates missing cases in union type handling or incomplete type guards. This is a compile-time safety feature, not a runtime error. TypeScript prevents you from writing code that assumes a value exists when its type system says it shouldn't.
First, find where the 'never' type originates. Look for union types and conditional checks:
type User = { type: 'admin'; name: string } | { type: 'guest' };
function getUserName(user: User): string {
if (user.type === 'admin') {
return user.name; // OK: user is narrowed to admin type
}
// ERROR: At this point, user is type 'never' if guest case isn't handled
return user.name; // Property 'name' does not exist on type 'never'
}Use your IDE's hover feature to see the inferred type at each point in your code.
Ensure every possible type in the union is accounted for:
type User = { type: 'admin'; name: string } | { type: 'guest' };
function getUserName(user: User): string {
if (user.type === 'admin') {
return user.name;
} else {
// Handle guest case explicitly
return 'Guest User';
}
}Or with switch statements:
type Status = 'pending' | 'success' | 'error';
function getStatusMessage(status: Status): string {
switch (status) {
case 'pending':
return 'Processing...';
case 'success':
return 'Completed successfully';
case 'error':
return 'An error occurred';
// No default needed - all cases covered
}
}Create a helper function to catch unhandled cases at compile time:
function assertNever(value: never): never {
throw new Error(`Unhandled value: ${JSON.stringify(value)}`);
}
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.side ** 2;
case 'triangle':
return 0.5 * shape.base * shape.height;
default:
// TypeScript ensures all cases are handled
return assertNever(shape);
}
}If you add a new shape type to the union, TypeScript will error at assertNever until you handle it.
Ensure type predicates cover all possibilities:
interface Dog { type: 'dog'; name: string; bark(): void }
interface Cat { type: 'cat'; name: string; meow(): void }
interface Bird { type: 'bird'; name: string; chirp(): void }
type Animal = Dog | Cat | Bird;
function isMammal(animal: Animal): animal is Dog | Cat {
return animal.type === 'dog' || animal.type === 'cat';
}
function handleAnimal(animal: Animal) {
if (isMammal(animal)) {
console.log(animal.name); // OK: Dog or Cat
if (animal.type === 'dog') {
animal.bark();
} else {
animal.meow();
}
} else {
// animal is Bird
console.log(animal.name); // OK: Bird
animal.chirp();
}
}Make sure every branch of conditional logic handles a valid type.
Avoid forcing types that TypeScript knows are impossible:
// ❌ DON'T: Type assertion that creates 'never'
type Impossible = string & number; // Type is 'never'
function badExample(value: string | number) {
if (typeof value === 'string') {
// value is string
} else if (typeof value === 'number') {
// value is number
} else {
// value is never here
const impossible = value as Impossible; // Creates problems
}
}
// ✅ DO: Let TypeScript infer types naturally
function goodExample(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase();
} else {
return value.toFixed(2);
}
}Let TypeScript's type inference work naturally rather than forcing assertions.
Enable strict mode to catch these errors early:
{
"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,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}Strict mode helps catch incomplete type handling before it causes runtime issues.
### Understanding TypeScript's Control Flow Analysis
TypeScript uses control flow analysis to narrow types based on conditional checks. When all possibilities are exhausted, the remaining type is 'never':
type Direction = 'north' | 'south' | 'east' | 'west';
function getCoordinates(dir: Direction): [number, number] {
switch (dir) {
case 'north': return [0, 1];
case 'south': return [0, -1];
case 'east': return [1, 0];
case 'west': return [-1, 0];
default:
// dir is 'never' here - all cases handled
const exhaustiveCheck: never = dir;
return exhaustiveCheck; // Compile-time safety
}
}### Discriminated Unions Best Practice
For complex type hierarchies, use a discriminant property (often called 'kind', 'type', or 'tag'):
type ApiResponse<T> =
| { status: 'success'; data: T }
| { status: 'error'; message: string }
| { status: 'loading' };
function handleResponse<T>(response: ApiResponse<T>) {
switch (response.status) {
case 'success':
return response.data;
case 'error':
throw new Error(response.message);
case 'loading':
return null;
default:
const _exhaustive: never = response;
return _exhaustive;
}
}### The 'never' Type in Generic Constraints
'never' can appear in generic contexts:
// Function that should never be called
function unreachable(): never {
throw new Error('This should never be called');
}
// Conditional type that results in 'never'
type Exclude<T, U> = T extends U ? never : T;
// Usage
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | null>; // string### Common Pitfalls
1. Empty arrays: [] has type never[] - an array that can't contain anything
2. Impossible intersections: string & number is never
3. Type guards with side effects: TypeScript can't track types through mutations
4. Function overloads: Incorrect overload signatures can create 'never' returns
### Testing Exhaustiveness
Add a test to ensure all union cases are handled:
// In your test suite
describe('exhaustiveness', () => {
it('handles all Status cases', () => {
const statuses: Status[] = ['pending', 'success', 'error'];
statuses.forEach(status => {
expect(() => getStatusMessage(status)).not.toThrow();
});
});
});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