How to fix Type 'T extends U ? X : Y' is not assignable to type 'Z' in TypeScript
This error occurs when TypeScript cannot guarantee that both branches of a conditional type are assignable to a target type. The fix involves understanding type constraints, distributivity, and ensuring all conditional branches satisfy the target type.
Quick fix
Fixes TS2322
// Problem: conditional type not assignable
type Result = T extends string ? string : number;
const value: string | number = /* conditional */ Result; // TS Error
// Solution: Check each branch
type TrueBranch = string; // OK for string | number
type FalseBranch = number; // OK for string | number
// Both branches ARE assignable, so the issue is deeper- Analyze both conditional branches
- Add extends constraints to prevent distributivity
- Ensure target type accommodates all conditional outcomes
- Add proper extends constraints to generic parameters
// Problem: conditional type not assignabletype Result = T extends string ? string : number;const value: string | number = /* conditional */ Result; // TS Error// Solution: Check each branchtype TrueBranch = string; // OK for string | numbertype FalseBranch = number; // OK for string | number// Both branches ARE assignable, so the issue is deeperType 'T extends U ? X : Y' is not assignable to type 'Z'
What this error means
When you create a conditional type with the syntax `T extends U ? X : Y`, TypeScript checks whether type `T` is assignable to type `U`. If yes, it resolves to `X`; otherwise it resolves to `Y`. The error "Conditional type is not assignable to" appears when TypeScript assigns or uses a conditional type but cannot prove that both the true branch (`X`) and false branch (`Y`) are assignable to the target type. This often happens because: 1. **Type narrowing in one branch**: The true branch narrows types differently than the false branch, creating an assignability mismatch 2. **Generic constraints ignored**: TypeScript's type resolution may ignore type parameter constraints 3. **Distributive conditional types**: When a naked type parameter triggers distributive behavior over unions, the result may not match the target 4. **Deferred vs. resolved conditionals**: If the condition depends on type variables, TypeScript may defer resolution, causing assignability issues
Typical symptoms
- TypeScript error appears when assigning a conditional type to a variable or parameter
- The error mentions both the conditional type and the target type it cannot be assigned to
- Wrapping the conditional type in a helper function sometimes works but direct assignment fails
- The error occurs even though both branches seem compatible with the target type
- Works correctly without the conditional type, but breaks when added
- Generic functions with conditional return types fail to satisfy their declared return type
Common causes
- Target type is too narrow for one branch of the conditional (most common)
- Type narrowing in the true branch doesn't apply to the false branch
- Using a naked type parameter in a distributive conditional when non-distributive behavior is needed
- Type parameter constraints are not considered in conditional type resolution
- The conditional type contains type variables that prevent resolution at assignment time
- Generic type parameters lack proper extends constraints to narrow possibilities
- Both branches of the conditional are correct individually but don't unify to the target type
How to fix "Type 'T extends U ? X : Y' is not assignable to type 'Z'"
First, understand what your conditional type resolves to in each case. Extract each branch separately and verify they match the target type:
// Problem: conditional type not assignable
type Result = T extends string ? string : number;
const value: string | number = /* conditional */ Result; // TS Error
// Solution: Check each branch
type TrueBranch = string; // OK for string | number
type FalseBranch = number; // OK for string | number
// Both branches ARE assignable, so the issue is deeperUse TypeScript's type checking to isolate which branch is problematic.
If your conditional type uses a naked type parameter, it becomes distributive over unions. Wrap the parameter in square brackets to disable distribution:
// Distributive (problematic in many cases)
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // (string | number)[]
// Non-distributive (correct)
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNonDist<string | number>; // (string | number)[]Distribution is useful for some patterns but can cause unexpected behavior when assigned to narrow types.
Make sure your target type is a union or broad enough to accept both branches:
// Problem: target type too narrow
type Conditional<T> = T extends string ? string : number;
const x: string = Conditional<boolean>; // TS Error: number not assignable to string
// Solution: Use union for target
const x: string | number = Conditional<boolean>; // OK
// Or: Use unknown/any if truly flexible (not recommended)
const x: unknown = Conditional<boolean>; // OK but loses type safetyThe target type must be wide enough to accept both the true and false branches.
Constrain your generic type parameters to help TypeScript understand valid inputs:
// Problem: no constraint, TypeScript can't narrow
type Extract<T> = T extends string ? T : never;
const x: string = Extract<string | number>; // TS Error
// Solution: constrain T
type ExtractString<T extends string | number> = T extends string ? T : never;
const x: string = ExtractString<"hello">; // OK
const y: string = ExtractString<string>; // OKConstraints help TypeScript understand which inputs are valid and resolve the type more accurately.
Instead of assigning a conditional type to a narrowly-typed variable, use the conditional in the type definition itself:
// Problem: conditional assigned to narrow type
function process<T>(value: T): string {
const result: string = T extends string ? value : "default"; // TS Error
return result;
}
// Solution: Use conditional in return type
function process<T>(value: T): T extends string ? string : string {
return T extends string ? value : "default"; // OK
}
// Or: More specific
function process<T extends string | number>(value: T): T extends string ? T : string {
return value as any;
}Let the conditional determine the type rather than forcing it into a pre-determined shape.
If your conditional type depends on unresolved type variables, TypeScript may defer it. Ensure variables are properly resolved:
// Problem: deferred conditional
type Flatten<T> = T extends Array<infer U> ? U : T;
// This is deferred because it depends on T
// Use in function
function flatten<T>(arr: T): Flatten<T> {
// TS may not resolve Flatten<T> at assignment time
return arr[0]; // Possible error
}
// Solution: Resolve with concrete types
function flatten<T extends Array<any>>(arr: T): T extends Array<infer U> ? U : T {
return arr[0];
}Make sure all type variables in your conditional are known at the point of use.
Advanced notes
### Understanding Distributive Conditional Types
When a conditional type's condition checks a naked type parameter (T extends U), it distributes over union types:
type ToArray<T> = T extends any ? T[] : never;
// T = string | number
// Distributes as: (string extends any ? string[] : never) | (number extends any ? number[] : never)
// Result: string[] | number[]
type Result = ToArray<string | number>; // string[] | number[]This is often desired, but can cause "not assignable" errors when:
- The target type expects non-distributed behavior
- Union members have conflicting assignability
- The distribution creates a union incompatible with the target
### Constraint Behavior in Conditionals
TypeScript deliberately ignores type parameter constraints when resolving conditionals to prevent unexpected behavior:
type Check<T extends string> = T extends any ? T : never;
// T is constrained to string, but the conditional doesn't use that constraint
// This prevents complexity but can feel counterintuitiveIf you need constraints to affect resolution, make them explicit in the conditional:
type Check<T extends string> = T extends string ? T : never; // Now uses constraint### Common Pattern: Conditional Type with Default
A common pattern is creating a conditional that safely handles all inputs:
type SafeExtract<T, Fallback = never> = T extends string ? T : Fallback;
// Usage with concrete fallback
const result: string | null = SafeExtract<string | number, null>; // OK### Recursive Conditional Types
Conditional types cannot reference themselves recursively directly:
// Invalid: would be infinite
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
// Workaround: use helper type (valid)
type FlattenHelper<T, Depth extends number = 1> =
Depth extends 0 ? T :
T extends Array<infer U> ? FlattenHelper<U, [-1, 0, 1, 2][Depth]> : T;Frequently asked questions
- What does "TS2322" mean?
- When you create a conditional type with the syntax T extends U ? X : Y, TypeScript checks whether type T is assignable to type U. If yes, it resolves to X; otherwise it resolves to Y.
- How do I fix "TS2322"?
- This error occurs when TypeScript cannot guarantee that both branches of a conditional type are assignable to a target type. The fix involves understanding type constraints, distributivity, and ensuring all conditional branches satisfy the target type.
- What causes "TS2322"?
- Target type is too narrow for one branch of the conditional (most common) Type narrowing in the true branch doesn't apply to the false branch Using a naked type parameter in a distributive conditional when non-distributive behavior is needed
- Is the "TS2322" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- ๐TypeScript Official Documentation
- ๐TypeScript Handbook - Conditional Types(Official Docs)
- ๐TypeScript Handbook - Advanced Types(Official Docs)
- ๐
- ๐