How to fix Template literal type produces union that is too complex in TypeScript

TypeScriptADVANCEDMEDIUMUpdated Dec 11, 2025· 107 views

This TypeScript error occurs when template literal types generate unions that exceed the compiler's complexity limits. Template literal types can create exponential union growth when used with conditional types or string manipulation patterns. Refactoring to use simpler type patterns or limiting string length typically resolves this issue.

Quick fix

Fixes TS2590

// ❌ This might be flagged as too complex
type Split<S extends string> = S extends `${infer First}${infer Rest}`
  ? [First, ...Split<Rest>]
  : [];

type Result = Split<"abcdefghijklmnopqrstuvwxyz">; // 26-character string
  1. Identify the problematic template literal type
  2. Limit string length in template literal types
  3. Avoid character-by-character processing
  4. Use non-distributive conditional types
typescript — error
// ❌ This might be flagged as too complex
type Split<S extends string> = S extends `${infer First}${infer Rest}`
? [First, ...Split<Rest>]
: [];
type Result = Split<"abcdefghijklmnopqrstuvwxyz">; // 26-character string
Template literal type produces union that is too complex

What this error means

TypeScript's template literal types allow you to manipulate and infer string literal types at compile time. However, when template literal types are combined with conditional types or recursive patterns, they can generate unions that grow exponentially in size, hitting TypeScript's internal complexity limits: 1. **Union explosion**: Template literal types that iterate over string characters or combine multiple string literals can create unions with thousands of members. 2. **Distributive behavior**: When template literal types are used in distributive conditional types, each union member spawns its own branch of computation. 3. **Recursive patterns**: Recursive template literal types (like splitting strings character by character) can create deeply nested type computations. The "too complex" error (TS2590) occurs when TypeScript cannot represent the resulting union type because it has grown beyond the compiler's capacity to track and reason about it. This is a protective measure to prevent infinite type recursion and ensure reasonable compilation times.

Typical symptoms

  • Error appears during type checking with 'Template literal type produces union that is too complex' message
  • The error occurs when using template literal types with conditional types or recursive patterns
  • TypeScript IntelliSense becomes slow or unresponsive in files with complex template literal types
  • The error vanishes when you simplify the template literal pattern or limit string length
  • It often happens with string manipulation utilities like Split, Join, or Replace types
  • Works with short strings but fails with longer input strings

Common causes

  1. Recursive template literal types without termination conditions (most common)
  2. Template literal types combined with distributive conditional types
  3. Attempting to process strings character-by-character with template literal inference
  4. Template literal types that generate Cartesian products of multiple string unions
  5. Deeply nested template literal type utilities
  6. Using template literal types to parse or validate complex string patterns
  7. Template literal types that reference themselves transitively through other type aliases

How to fix "Template literal type produces union that is too complex"

1Identify the problematic template literal type

The error message will point to a specific line. Look for template literal types with inference patterns:

typescript
// ❌ This might be flagged as too complex
type Split<S extends string> = S extends `${infer First}${infer Rest}`
  ? [First, ...Split<Rest>]
  : [];

type Result = Split<"abcdefghijklmnopqrstuvwxyz">; // 26-character string

Look for:
- Recursive template literal types
- Template literal types in conditional type branches
- Multiple infer keywords in a single template literal
- Template literal types processing long strings

2Limit string length in template literal types

Add explicit length limits to prevent infinite recursion:

typescript
// ✅ Better: Explicit length limit
type Split<
  S extends string,
  MaxDepth extends readonly any[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  Acc extends string[] = []
> = MaxDepth extends readonly [infer _ extends any, ...infer Rest]
  ? S extends `${infer First}${infer Remainder}`
    ? Split<Remainder, Rest, [...Acc, First]>
    : Acc
  : Acc;

// Usage with limit
type Result = Split<"hello">; // ["h", "e", "l", "l", "o"]
type LongResult = Split<"abcdefghijklmnopqrstuvwxyz">; // Only processes first 10 chars

This prevents the type from recursing indefinitely.

3Avoid character-by-character processing

Instead of processing each character, work with larger chunks:

typescript
// ❌ Character-by-character (expensive)
type Reverse<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${Reverse<Rest>}${First}`
  : S;

// ✅ Better: Use built-in string utilities or runtime
type Reverse<S extends string> = string; // Accept any string

// Or use a simpler approach
type LastChar<S extends string> = S extends `${string}${infer Last}` ? Last : S;
type FirstChar<S extends string> = S extends `${infer First}${string}` ? First : S;

Consider whether you really need character-level type manipulation.

4Use non-distributive conditional types

Wrap template literal types in square brackets to prevent distribution:

typescript
// ❌ Distributive - creates union explosion
type ExtractPrefix<T extends string> = T extends `${infer Prefix}-${string}`
  ? Prefix
  : never;

type Result = ExtractPrefix<"foo-bar" | "baz-qux" | "quux-corge">;
// Evaluates to: "foo" | "baz" | "quux" (but can explode with complex unions)

// ✅ Better: Non-distributive
type ExtractPrefix<T extends string> = [T] extends [`${infer Prefix}-${string}`]
  ? Prefix
  : never;

This keeps complexity linear instead of exponential.

5Break complex template literal types into simpler parts

Split large template literal types into smaller, named helpers:

typescript
// ❌ One complex type
type ParseRoute<T extends string> = T extends `/${infer Segment}/${infer Rest}`
  ? [Segment, ...ParseRoute<`/${Rest}`>]
  : T extends `/${infer Segment}`
  ? [Segment]
  : [];

// ✅ Better: Separate helpers
type ExtractSegment<T extends string> = T extends `/${infer Segment}${infer Rest}`
  ? { segment: Segment; rest: Rest }
  : never;

type ParseSegments<T extends string, Acc extends string[] = []> =
  ExtractSegment<T> extends { segment: infer S extends string; rest: infer R extends string }
    ? ParseSegments<R, [...Acc, S]>
    : Acc;

Smaller types are easier for TypeScript to reason about.

6Use template literal types for validation, not transformation

Use template literal types to validate string shapes, not to transform them:

typescript
// ✅ Good: Validation only
type IsKebabCase<S extends string> = S extends `${string}-${string}`
  ? true
  : false;

type ValidRoute<S extends string> = S extends `/${string}`
  ? S
  : never;

// Runtime transformation
function toKebabCase(str: string): string {
  return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
}

Leave complex string manipulation to runtime JavaScript.

7Upgrade TypeScript version

Newer TypeScript versions have optimizations for template literal types:

- TypeScript 4.1: Introduced template literal types
- TypeScript 4.3: Performance improvements
- TypeScript 4.4: Better error messages
- TypeScript 4.5+: Further optimizations

bash
npm install --save-dev typescript@latest

Upgrading may automatically resolve some complexity issues.

8Verify the fix

After refactoring:

1. Run TypeScript type checking:

bash
npx tsc --noEmit

2. Check that the "too complex" error is gone
3. Verify IntelliSense works correctly
4. Test with various string inputs:

typescript
// Should work without errors
type Test1 = Split<"hello">;
type Test2 = ExtractPrefix<"foo-bar">;
type Test3 = IsKebabCase<"my-component">;

Advanced notes

### TypeScript's Internal Limits

Template literal types hit several internal limits:

1. Union Size Limit: Unions with more than ~1000 members become problematic
2. Instantiation Depth: Maximum 50 for general instantiation
3. Tail Recursion Depth: Maximum 1000 for optimized recursion

### When Template Literal Types Are Appropriate

Use template literal types for:
- Validating string formats (URLs, IDs, routes)
- Simple string manipulation with known bounds
- Type-safe string concatenation
- Branded string types

Avoid template literal types for:
- Character-by-character string processing
- Complex string transformations
- Parsing arbitrary-length strings
- Recursive string algorithms

### Alternative Approaches

1. Runtime validation with type guards:

typescript
function isKebabCase(str: string): str is KebabCase {
  return /^[a-z]+(-[a-z]+)*$/.test(str);
}

2. Branded types:

typescript
type KebabCase = string & { __brand: 'kebab-case' };
function toKebabCase(str: string): KebabCase {
  return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase()) as KebabCase;
}

3. Const assertions:

typescript
const routes = ['/home', '/about', '/contact'] as const;
type Route = typeof routes[number]; // '/home' | '/about' | '/contact'

### Debugging Template Literal Types

Use intermediate type aliases to see where complexity explodes:

typescript
type Step1<S extends string> = S extends `${infer A}${infer B}` ? [A, B] : never;
type inspect1 = Step1<'hello'>; // Check this step

type Step2<S extends string> = Step1<S> extends [infer A, infer B]
  ? [B, A]
  : never;
type inspect2 = Step2<'hello'>; // Check if complexity compounds

### References

- TypeScript 4.1 Release Notes: Template Literal Types
- TypeScript Handbook: Template Literal Types
- Type Challenges: Template Literal Type exercises

### Related Errors

- "Conditional type produces union that is too complex"
- "Type instantiation is excessively deep and possibly infinite"
- "Expression produces a union type that is too complex to represent"

Frequently asked questions

What does "TS2590" mean?
TypeScript's template literal types allow you to manipulate and infer string literal types at compile time. However, when template literal types are combined with conditional types or recursive patterns, they can generate unions that grow exponentially in size, hitting TypeScript's internal complexity limits: 1. Union explosion: Template literal types that iterate over string characters or combine multiple string literals can create unions with thousands of members.
How do I fix "TS2590"?
This TypeScript error occurs when template literal types generate unions that exceed the compiler's complexity limits. Template literal types can create exponential union growth when used with conditional types or string manipulation patterns. Refactoring to use simpler type patterns or limiting string length typically resolves this issue.
What causes "TS2590"?
Recursive template literal types without termination conditions (most common) Template literal types combined with distributive conditional types Attempting to process strings character-by-character with template literal inference
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