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.
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
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.
### 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;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