This TypeScript error occurs when you try to access properties on a value that TypeScript has determined is unreachable (type 'never'), often combined with non-null assertions (!). The fix involves proper type narrowing and avoiding unsafe assertions on impossible values.
The 'never' type in TypeScript represents values that should never occur—an impossible state. When you see this error with a non-null assertion (!), it means you're trying to force TypeScript to treat an impossible value as if it exists. This happens when: 1. TypeScript's control flow analysis determines a code path is unreachable 2. You use a non-null assertion (!) on a value that TypeScript knows is 'never' 3. You're trying to access properties on a value that logically cannot exist The non-null assertion operator (!) tells TypeScript "trust me, this value isn't null or undefined," but it doesn't work on 'never' because 'never' means "this value can never exist at all." TypeScript is warning you that your code logic has a contradiction: you're asserting something exists when the type system says it's impossible.
First, locate the ! operator that's causing the error. Look for patterns like:
type Status = 'pending' | 'success' | 'error';
function getMessage(status: Status): string {
if (status === 'pending') {
return 'Processing...';
} else if (status === 'success') {
return 'Done!';
}
// At this point, TypeScript knows status is 'never'
// But if you try to use ! here, you'll get the error:
return status!.toUpperCase(); // ERROR: Cannot access property of type never
}Use your IDE's hover feature to see what type TypeScript infers at the point of the ! operator.
Instead of forcing TypeScript with !, handle the 'never' case properly:
type Status = 'pending' | 'success' | 'error';
function getMessage(status: Status): string {
if (status === 'pending') {
return 'Processing...';
} else if (status === 'success') {
return 'Done!';
} else {
// Handle the 'error' case explicitly
return 'Error occurred';
}
// No need for ! operator
}Or use a switch statement with all cases covered:
function getMessage(status: Status): string {
switch (status) {
case 'pending':
return 'Processing...';
case 'success':
return 'Done!';
case 'error':
return 'Error occurred';
}
}Create an exhaustiveness check function that catches unhandled cases at compile time:
function assertNever(value: never): never {
throw new Error(`Unhandled value: ${value}`);
}
type Status = 'pending' | 'success' | 'error';
function getMessage(status: Status): string {
switch (status) {
case 'pending':
return 'Processing...';
case 'success':
return 'Done!';
default:
// TypeScript knows this is 'never' if all cases are handled
assertNever(status);
}
}If you add a new value to the Status union and forget to handle it, TypeScript will error at the assertNever call, preventing runtime errors.
When TypeScript has narrowed a type to 'never', don't use !—instead, restructure your code:
// BAD: Using ! on never
function processValue(value: string | null) {
if (value === null) {
throw new Error('Value is null');
}
// value is string here
return value.toUpperCase();
}
// GOOD: Proper null handling without !
function processValue(value: string | null): string {
if (value === null) {
throw new Error('Value is null');
}
return value.toUpperCase();
}
// BAD with unions:
type Result = { type: 'data'; data: string } | { type: 'error' };
function handleResult(result: Result) {
if (result.type === 'data') {
console.log(result.data);
}
// result is 'never' here (type 'error' not handled)
// Don't use: console.log(result!.type);
}Define explicit type guards instead of using non-null assertions:
interface User { id: string; name: string }
interface Guest { sessionId: string }
type Visitor = User | Guest;
function isUser(visitor: Visitor): visitor is User {
return 'id' in visitor && 'name' in visitor;
}
function getName(visitor: Visitor): string {
if (isUser(visitor)) {
return visitor.name; // Safe access, no ! needed
} else {
return 'Guest'; // Handle Guest case
}
}
// Alternative: discriminated union
type Visitor2 =
| { type: 'user'; id: string; name: string }
| { type: 'guest'; sessionId: string };
function getName2(visitor: Visitor2): string {
switch (visitor.type) {
case 'user':
return visitor.name; // Safe
case 'guest':
return 'Guest'; // Handled
}
}Ensure your tsconfig.json has appropriate strictness settings:
{
"compilerOptions": {
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}These settings help catch type errors early, including unsafe non-null assertions on 'never' types.
### Understanding the Intersection of 'never' and Non-Null Assertions
The non-null assertion operator (!) works by removing 'null' and 'undefined' from a type. However, 'never' is different—it's not about nullability, but about impossibility.
// What ! does:
type MaybeString = string | null | undefined;
type DefinitelyString = MaybeString!; // string
// What happens with never:
type Impossible = never;
type StillImpossible = Impossible!; // still never### When Non-Null Assertions Are Appropriate
Use ! only when:
1. You know better than TypeScript (e.g., external data validation)
2. The value comes from outside TypeScript's type system
3. You've validated the value at runtime
Never use ! to override TypeScript's control flow analysis—it's telling you about a logical contradiction in your code.
### Pattern: Exhaustive Checking with Discriminated Unions
For complex scenarios, use discriminated unions with exhaustive checking:
type ApiResponse<T> =
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string };
function handleResponse<T>(response: ApiResponse<T>): string {
switch (response.status) {
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${response.data}`;
case 'error':
return `Error: ${response.message}`;
default:
// TypeScript ensures this is never reached
const exhaustiveCheck: never = response;
return exhaustiveCheck;
}
}### Edge Case: Async Functions and Promises
'never' can appear with promise types:
async function alwaysRejects(): Promise<never> {
throw new Error('Always fails');
}
// Trying to use ! on the result is meaningless
async function badExample() {
const result = await alwaysRejects();
// result is 'never' - can't use result!
}### Migration Strategy
If you're refactoring code with many ! operators:
1. First, enable strictNullChecks
2. Replace ! with proper null checks
3. Use optional chaining (?.) where appropriate
4. Add explicit type guards for complex cases
5. Use exhaustive checking for union types
### Performance Considerations
TypeScript's control flow analysis that produces 'never' types happens at compile time and has no runtime cost. Removing ! operators improves code safety without affecting performance.
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