This TypeScript error occurs when you try to assign a union type to a variable or parameter expecting a more specific type. The compiler cannot guarantee type safety without explicit narrowing.
This error appears when TypeScript's type checker detects an incompatible assignment involving union types. A union type (X | Y) represents a value that could be either type X or type Y. When you try to assign this union to a variable expecting a specific type Z, TypeScript cannot guarantee that the value will always be compatible with Z. The error is TypeScript's way of preventing potential runtime errors. Since a union type could be any of its constituent types, assigning it to a narrower type without verification could lead to accessing properties or methods that don't exist on all union members. This commonly occurs when working with function parameters, return types, conditional logic, or when TypeScript infers a broader type than you intended. The solution typically involves narrowing the type using type guards, type assertions, or restructuring your code to handle each union member explicitly.
The most common solution is to use type guards to narrow the union type before assignment. TypeScript supports several type guard patterns:
// Using typeof for primitive types
function processValue(value: string | number) {
if (typeof value === 'string') {
// TypeScript knows value is string here
const result: string = value; // ✓ Works
} else {
// TypeScript knows value is number here
const result: number = value; // ✓ Works
}
}
// Using 'in' operator for object properties
type Dog = { bark: () => void };
type Cat = { meow: () => void };
function handlePet(pet: Dog | Cat) {
if ('bark' in pet) {
const dog: Dog = pet; // ✓ Works
} else {
const cat: Cat = pet; // ✓ Works
}
}Type guards let TypeScript understand which specific type you're working with in each branch.
Add a discriminant property to each type in the union to enable automatic type narrowing:
type Success = { status: 'success'; data: string };
type Error = { status: 'error'; message: string };
type Result = Success | Error;
function handleResult(result: Result) {
// TypeScript narrows based on discriminant
if (result.status === 'success') {
const data: string = result.data; // ✓ Works
} else {
const message: string = result.message; // ✓ Works
}
}
// Switch statements also work
function processResult(result: Result) {
switch (result.status) {
case 'success':
return result.data; // TypeScript knows this is Success
case 'error':
return result.message; // TypeScript knows this is Error
}
}Discriminated unions are the preferred pattern for complex union types.
If you have external knowledge that a value is a specific type, use type assertions:
type Animal = Dog | Cat | Bird;
function getDog(animal: Animal): Dog {
// Use 'as' type assertion
return animal as Dog;
}
// Or angle bracket syntax (not in JSX)
function getCat(animal: Animal): Cat {
return <Cat>animal;
}
// Better: combine with runtime check
function getSafeDog(animal: Animal): Dog {
if ('bark' in animal) {
return animal as Dog; // ✓ Safe assertion
}
throw new Error('Not a dog');
}Warning: Type assertions bypass TypeScript's type checking. Only use them when you're certain about the type, preferably after runtime validation.
TypeScript sometimes infers union types when you expect a specific type. Add explicit annotations:
// Problem: TypeScript infers string | number
let value = Math.random() > 0.5 ? 'hello' : 42;
const str: string = value; // ✗ Error
// Solution 1: Explicit annotation
let value: string = Math.random() > 0.5 ? 'hello' : 'world';
const str: string = value; // ✓ Works
// Solution 2: as const for literal types
const config = {
mode: 'development' // inferred as string
} as const; // now mode is 'development' literal
// Solution 3: Type assertion at initialization
let value = (Math.random() > 0.5 ? 'hello' : 42) as string;Explicit types help TypeScript understand your intent and prevent overly broad inference.
For reusable type narrowing logic, implement custom type predicate functions:
interface User {
id: number;
name: string;
}
interface Guest {
sessionId: string;
}
// Type predicate function
function isUser(account: User | Guest): account is User {
return 'id' in account && 'name' in account;
}
function greet(account: User | Guest) {
if (isUser(account)) {
// TypeScript knows account is User
console.log(`Hello ${account.name}`); // ✓ Works
} else {
// TypeScript knows account is Guest
console.log(`Session: ${account.sessionId}`); // ✓ Works
}
}Type predicates are functions that return value is Type and help TypeScript narrow types in calling code.
Sometimes the best solution is to update your code to handle union types properly:
// Instead of forcing a specific type
function badProcess(value: string) {
return value.toUpperCase();
}
// Accept the union and handle both cases
function goodProcess(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toString();
}
// Or use generics to preserve the type
function identity<T>(value: T): T {
return value;
}
const str = identity('hello'); // Type: string
const num = identity(42); // Type: numberAdapting your function signatures to accept union types often produces cleaner, more flexible code than forcing type narrowing everywhere.
Structural typing implications: TypeScript uses structural typing, meaning type compatibility is determined by structure, not name. A union type X | Y is only assignable to type Z if both X and Y are assignable to Z. This can lead to unexpected errors when types have similar but not identical structures.
Variance and union types: TypeScript unions are invariant in some positions. For example, function parameters are contravariant, which affects how union types can be used in callbacks and event handlers. Understanding variance helps predict when union type assignments will fail.
Excess property checking: When assigning object literals to union types, TypeScript performs excess property checking. This can cause errors even when the object would be structurally compatible, because TypeScript tries to match the literal to a specific union member first.
Union distribution in conditional types: When using conditional types with unions, TypeScript distributes the condition over each union member. This can create unexpected results when trying to assign conditional types involving unions.
Performance considerations: Excessive use of union types with many members can slow down TypeScript compilation. If you have unions with 10+ members, consider using discriminated unions or refactoring into a more hierarchical type structure.
Null and undefined handling: With strictNullChecks enabled, TypeScript treats null and undefined as separate types. This means T | null | undefined requires explicit handling for all three cases. Use optional chaining (?.) and nullish coalescing (??) to simplify null checks.
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