How to fix Conditional type branch produces a union type that is too complex to represent in TypeScript
This error occurs when TypeScript's conditional types generate union branches that exceed the compiler's complexity limits. This typically happens with recursive conditional types or deeply nested generic constraints. Refactoring to break down complex type logic into simpler pieces usually resolves the issue.
Quick fix
Fixes TS2590
// ❌ This might be flagged as too complex
type ExtractFromUnion<T> = T extends { prop: infer U }
? U
: T extends { other: infer V }
? V
: never;
type Result = ExtractFromUnion<
| { prop: string }
| { other: number }
| { prop: boolean }
// ... many more union members
>;- Identify which type is too complex
- Break down the conditional type
- Use object properties instead of recursion
- Avoid distributive conditionals on large unions
// ❌ This might be flagged as too complextype ExtractFromUnion<T> = T extends { prop: infer U }? U: T extends { other: infer V }? V: never;type Result = ExtractFromUnion<| { prop: string }| { other: number }| { prop: boolean }// ... many more union members>;Conditional type branch produces a union type that is too complex to represent
What this error means
TypeScript's type system has built-in limits to prevent infinite type recursion and ensure reasonable compiler performance. When you use conditional types (the ternary operator for types), especially in recursive or distributive scenarios with unions, TypeScript may hit these internal limits: 1. **Instantiation depth limit**: Tracks how deeply nested type parameters can be instantiated (default: 50, with a maximum tail recursion depth of 1000). 2. **Tail recursion depth limit**: When a conditional type references itself, TypeScript tries to optimize with tail recursion, but has a maximum depth of 1000. 3. **Union explosion**: When distributive conditional types process union members, each union element may spawn multiple type branches, causing exponential growth. The "too complex to represent" error (TS2590) occurs when TypeScript gives up trying to resolve a union that has grown too large or deeply nested. The compiler cannot meaningfully track what the final type should be because the complexity has exceeded its reasoning capability.
Typical symptoms
- Error appears during type checking with 'too complex to represent' message
- The error vanishes when you simplify or break apart the conditional type
- It happens with recursive conditional types that process unions or strings
- TypeScript IntelliSense becomes slow or times out in related files
- Works in isolation but fails in larger projects with accumulated type complexity
- The error points to a type alias or function signature that seems reasonable in isolation
Common causes
- Recursive conditional types without clear termination conditions (most common)
- Distributive conditional types applied to large unions (each union member doubles the branches)
- Deeply nested generic constraints with multiple type parameters
- Attempting to extract/split large literal string types character by character
- Conditional types that reference themselves transitively through other type aliases
- Using conditional types in constraint positions on deeply generic functions
- Type utilities that weren't designed with recursive safety in mind (e.g., deep Omit, Pick on large interfaces)
How to fix "Conditional type branch produces a union type that is too complex to represent"
The TypeScript error message will point to a specific line. Look at the type definition:
// ❌ This might be flagged as too complex
type ExtractFromUnion<T> = T extends { prop: infer U }
? U
: T extends { other: infer V }
? V
: never;
type Result = ExtractFromUnion<
| { prop: string }
| { other: number }
| { prop: boolean }
// ... many more union members
>;Look for:
- Recursive conditional type definitions
- Deeply nested generics
- Distributive conditionals applied to large unions
- Character-by-character string processing
Instead of one large conditional, split it into smaller, named helpers:
// ✅ Better: Simpler intermediate steps
type HasProp<T> = T extends { prop: infer U } ? U : never;
type HasOther<T> = T extends { other: infer V } ? V : never;
type ExtractValue<T> = HasProp<T> | HasOther<T>;This reduces depth and makes the type easier to reason about.
TypeScript defers instantiation of types in object/array properties. Use this to avoid hitting recursion limits:
// ❌ Deep recursion—hits limit
type DeepNest<T, N extends number> = N extends 0
? T
: DeepNest<{ inner: T }, [any, ...any[]]>;
// ✅ Better: Put recursion in a property
type DeepProperty<T, N extends number> = {
value: N extends 0 ? T : DeepProperty<T, [any, ...any[]]>;
};Deferring the type instantiation prevents it from counting against your depth limit.
Distributive conditional types iterate over each union member, causing exponential growth:
// ❌ Distributive—one branch per union member × number of conditions
type Extract<T, K extends PropertyKey> = T extends Record<K, any>
? T[K]
: never;
type Result = Extract<
| { a: string } | { b: number } | { c: boolean } | ...many more
| Record<string, any>
>;
// ✅ Better: Non-distributive conditional (use square brackets)
type Extract<T, K extends PropertyKey> = [T] extends [Record<K, any>]
? T[K]
: never;Wrapping in square brackets prevents distribution and keeps complexity linear.
For recursive types, add a depth counter to prevent infinite recursion:
// ❌ Unlimited recursion
type FlattenArray<T> = T extends (infer U)[]
? FlattenArray<U>
: T;
// ✅ Better: Explicit depth limit
type FlattenArray<T, D extends number = 5> = D extends 0
? T
: T extends (infer U)[]
? FlattenArray<U, [any, ...any[]] extends [...infer R] ? R['length'] : 0>
: T;
// Or use a simple numeric count:
type Flatten<T, Depth extends readonly any[] = [1, 2, 3, 4, 5]> = Depth extends readonly [infer _ extends any, ...infer Rest]
? T extends (infer U)[]
? Flatten<U, Rest>
: T
: T;Character-by-character string processing is expensive. Limit string lengths:
// ❌ Dangerous—fails on strings longer than ~20 chars
type Split<S extends string, Acc extends string[] = []> = S extends `${infer First}${infer Rest}`
? Split<Rest, [...Acc, First]>
: Acc;
// ✅ Better: Explicit length limit
type Split<
S extends string,
MaxLen extends readonly any[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
Acc extends string[] = []
> = MaxLen extends readonly [infer _ extends any, ...infer Rest]
? S extends `${infer First}${infer Remainder}`
? Split<Remainder, Rest, [...Acc, First]>
: Acc
: Acc;Consider using JavaScript at runtime instead of trying to process large strings at type-check time.
Newer TypeScript versions have optimizations:
- TypeScript 4.1+: Tail-recursive conditional types compile in a loop (no call stack)
- TypeScript 4.5+: Performance improvements for complex type operations
- TypeScript 5.0+: Further optimizations and better error messages
npm install --save-dev typescript@latestIf you're on an older version, upgrading may automatically resolve the issue.
After refactoring:
1. Run TypeScript type checking:
npx tsc --noEmit2. Check that your code still compiles without "too complex" errors
3. Verify that IntelliSense works and shows correct types
4. Test that your type utilities produce the expected results:
import type { YourType } from './types';
// Should show inferred type without error
const test: YourType<SomeInput> = /* ... */;Advanced notes
### Internal Limits in TypeScript
TypeScript enforces three key limits to prevent runaway compilation:
1. Instantiation Depth: Maximum 50 for general instantiation, 100 for conditional types
2. Tail Recursion Depth: Maximum 1000 when recursion is tail-recursive (recommended optimization)
3. Union Flattening: Unions with more than ~1000 members become problematic
### When to Use Runtime Instead
If your type utility requires:
- Processing strings character-by-character
- Deep tree recursion on large structures
- Complex set operations (union, intersection, difference)
Consider using functions with generics instead of pure type manipulation:
// Type-level: only validate the shape
type ValidConfig<T> = T extends { key: string } ? T : never;
// Runtime: actual processing
function processConfig<T extends { key: string }>(config: T) {
// Do expensive operations here
return config;
}### Debugging Complex Types
Use type inspect = ... to isolate where complexity explodes:
type Step1<T> = T extends SomeCondition ? A : B;
type inspect1 = Step1<YourType>; // Check if this is complex
type Step2<T> = Step1<T> extends Another ? C : D;
type inspect2 = Step2<YourType>; // Check if complexity compounds### References
- TypeScript PR #45025: "Increase type instantiation depth limit" (allows tuning)
- TypeScript PR #45711: "Tail recursive evaluation of conditional types" (optimization)
- Type instantiation is limited to prevent infinite loops and compiler hangs
### Related Tools
- ts-expect-error: Suppress the error temporarily while refactoring
- type-fest: Popular library with well-tested complex type utilities (good examples)
- TypeScript Playground: Test type utilities in isolation: https://www.typescriptlang.org/play
Frequently asked questions
- What does "TS2590" mean?
- TypeScript's type system has built-in limits to prevent infinite type recursion and ensure reasonable compiler performance. When you use conditional types (the ternary operator for types), especially in recursive or distributive scenarios with unions, TypeScript may hit these internal limits: 1. Instantiation depth limit: Tracks how deeply nested type parameters can be instantiated (default: 50, with a maximum tail recursion depth of 1000).
- How do I fix "TS2590"?
- This error occurs when TypeScript's conditional types generate union branches that exceed the compiler's complexity limits. This typically happens with recursive conditional types or deeply nested generic constraints. Refactoring to break down complex type logic into simpler pieces usually resolves the issue.
- What causes "TS2590"?
- Recursive conditional types without clear termination conditions (most common) Distributive conditional types applied to large unions (each union member doubles the branches) Deeply nested generic constraints with multiple type parameters
- Is the "TS2590" 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)
- 🐙
- 🐙
- 🐙
- 🐙
- 🐙