This TypeScript error occurs when you use a type assertion (as) to cast a value to a type that conflicts with type narrowing already performed. TypeScript detects that your assertion contradicts what it has already inferred or narrowed the type to be. The fix involves either removing the unnecessary assertion or adjusting your code flow to work with the narrowed type.
TypeScript performs type narrowing through control flow analysis—when you check if a value matches a certain type using if statements, type guards, or other conditions, TypeScript automatically narrows the type within that block. If you then try to assert the value as a type that contradicts this narrowed type, TypeScript raises an error because the assertion is logically impossible and likely indicates a mistake in your code. Type assertions are meant to tell TypeScript 'I know better than the compiler,' but TypeScript still prevents contradictory assertions that would be unsafe.
First, identify what type TypeScript has already narrowed the value to at the point of error. Look at preceding control flow statements—typeof checks, instanceof checks, or type guards.
// Example of type narrowing
function process(value: string | number) {
if (typeof value === 'string') {
// Inside this block, TypeScript has narrowed value to 'string'
const result = value as number; // ERROR: contradicts narrowing
}
}Hover over the variable in your editor to see what TypeScript thinks the type is at that location.
The error occurs because you're trying to assert a value to a type that is logically incompatible with the narrowed type. After TypeScript narrows a type, it tracks that narrowing. If you assert to something contradictory, it means:
- You're asserting string to number after a typeof check confirmed it's a string
- You're asserting Dog to Cat after instanceof proved it's a Dog
- You're asserting to a mutually exclusive union member
function example(value: string | number | boolean) {
if (typeof value === 'string') {
// value is narrowed to 'string'
// These assertions all contradict the narrowed type:
const n = value as number; // ERROR
const b = value as boolean; // ERROR
const u = value as unknown; // OK - unknown is broader
}
}Most commonly, the simplest fix is to remove the assertion entirely. If TypeScript has already narrowed the type correctly, you don't need to assert it.
function process(value: string | number) {
if (typeof value === 'string') {
// Just use 'value' as-is, it's already 'string'
const upper = value.toUpperCase(); // Works, no assertion needed
// This is wrong:
const result = value as number; // ERROR
}
}If you need to work with a different type, restructure the control flow so that the narrowed type matches what you want to assert to.
function process(value: string | number) {
if (typeof value === 'number') {
// Now value is narrowed to 'number'
const doubled = value * 2; // Works without assertion
} else {
// In the else block, value is narrowed to 'string'
const upper = value.toUpperCase(); // Works without assertion
}
}Rely on TypeScript's type narrowing (through control flow) rather than assertions. This is more reliable and safer.
function example(value: string | number | undefined) {
// Good: Let TypeScript narrow the type naturally
if (value === undefined) {
return 'no value';
}
if (typeof value === 'string') {
return value.length; // value is 'string', no assertion needed
}
// Here value must be 'number'
return value.toFixed(2); // No assertion needed
}If you absolutely need to assert to an incompatible type (which should be rare), use the escape hatch pattern: as unknown as Type. This tells TypeScript you understand the risk.
function unsafeConversion(value: string | number) {
if (typeof value === 'string') {
// If you truly need to force this (usually indicates a logic error):
const forced = value as unknown as boolean;
}
}Warning: Only use this when you're certain and understand the runtime implications.
Run TypeScript compiler to confirm the error is resolved:
npx tsc --noEmitCheck that your code compiles without errors and that the logic is still correct.
### Understanding Type Narrowing Scopes
Type narrowing is scoped to the block where it occurs. Once you exit that block, the narrowing is lost:
function example(value: string | number) {
if (typeof value === 'string') {
// value is 'string' here
}
// value is back to 'string | number' here
// You'd need to assert or check type again
}### Custom Type Guards
When working with custom type guards, TypeScript narrows based on the guard function's return type:
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(value: string | number) {
if (isString(value)) {
// value is narrowed to 'string'
const result = value as number; // ERROR: contradicts narrowing
}
}### Assertion Functions
Use assertion functions with asserts keyword instead of relying on type assertions:
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Not a string');
}
}
function process(value: string | number) {
assertIsString(value);
// After this line, TypeScript knows value is 'string'
// No assertion needed, narrowing is proven by the assertion function
}### Union Type Handling
When working with discriminated unions, let TypeScript do the narrowing:
type Result =
| { success: true; data: string }
| { success: false; error: string };
function handle(result: Result) {
if (result.success) {
// TypeScript narrows to { success: true; data: string }
const data = result.data; // No assertion needed
// This contradicts:
const error = result.error; // ERROR: 'error' doesn't exist on narrowed type
}
}The key principle: Trust TypeScript's type narrowing analysis. If you're fighting against it with assertions, you likely have a logic error in your code.
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