This TypeScript error occurs when using conditional types with union types that contain 'never', often in complex generic type transformations. The issue arises from how distributive conditional types handle the never type during union distribution. The fix typically involves wrapping types in tuples or adjusting type constraints.
The "Cannot extend with union type containing 'never'" error appears when TypeScript's type system encounters issues with distributive conditional types that include the 'never' type in a union. This is a relatively advanced error that typically occurs when working with complex generic type transformations, utility types, or mapped types. The 'never' type in TypeScript represents values that never occur - it's the bottom type in TypeScript's type hierarchy. In union types, 'never' normally gets filtered out automatically (similar to how zero works in addition). However, when used with conditional types that employ the 'extends' keyword, the presence of 'never' in a union can cause distribution problems. Distributive conditional types automatically distribute over union types during instantiation. When a union type is plugged into a conditional type, TypeScript applies the condition to each member of the union individually. If that union contains 'never' or resolves to 'never' in certain branches, TypeScript may raise this error when it cannot properly evaluate the extends constraint. Common scenarios include: 1. **Type filtering utilities**: Attempting to filter out types from a union using conditional types 2. **Generic constraints**: Using union types with 'never' as generic constraints 3. **Nested conditional types**: Complex type transformations where intermediate results include 'never' 4. **Distributive vs non-distributive behavior**: Confusion between how naked type parameters distribute versus wrapped types
The most common fix is to wrap your type parameters in tuple brackets to prevent distributive behavior:
// WRONG - Distributive conditional type causes issues with never
type IsString<T> = T extends string ? true : false;
type Result = IsString<string | never>; // May cause error
// CORRECT - Wrap in tuples to avoid distribution
type IsString<T> = [T] extends [string] ? true : false;
type Result = IsString<string | never>; // Works correctlyThis pattern prevents TypeScript from distributing the conditional type over each union member:
// Example: Filtering union types
type Filter<T, U> = [T] extends [U] ? T : never;
// Example: Checking for never
type IsNever<T> = [T] extends [never] ? true : false;
type Check1 = IsNever<never>; // true
type Check2 = IsNever<string>; // falseThe tuple wrapping treats the entire union as a single unit rather than distributing over each member.
If you need distributive behavior but want to avoid the error, use a generic context with the 'infer' keyword:
// WRONG - Non-distributive comparison may fail
type ExtractString<T> = T extends string ? T : never;
// CORRECT - Use infer to create generic context
type ExtractString<T> = T extends infer U
? U extends string
? U
: never
: never;
// Example usage
type Result = ExtractString<string | number | boolean>;
// Result: stringThis creates a conditional type with a generic parameter context, allowing iteration over union members:
// Filtering out null and undefined
type NonNullable<T> = T extends infer U
? U extends null | undefined
? never
: U
: never;
type Clean = NonNullable<string | null | number | undefined>;
// Clean: string | numberVerify whether your union type is unintentionally resolving to never:
// Debug helper to inspect types
type Debug<T> = { [K in keyof T]: T[K] };
// Check what your type resolves to
type MyUnion = string | never;
type Resolved = Debug<{ value: MyUnion }>;
// Hover over Resolved in your IDE to see: { value: string }
// Common case: Generic parameter that might be never
type SafeExtract<T, U> = T extends never
? never
: T extends U
? T
: never;If your union contains never unintentionally, find where it's being produced:
// This produces never if no overlap exists
type Overlap<T, U> = Extract<T, U>;
type NoMatch = Overlap<string, number>; // never
// Fix by handling the never case explicitly
type SafeOverlap<T, U> = Extract<T, U> extends never
? "no overlap"
: Extract<T, U>;Add explicit checks for the never type before performing extends operations:
// WRONG - Doesn't handle never case
type Transform<T> = T extends string ? string[] : T;
// CORRECT - Check for never first
type Transform<T> = [T] extends [never]
? never
: T extends string
? string[]
: T;
// Example with union filtering
type FilterOut<T, U> = T extends never
? never
: T extends U
? never
: T;
type Numbers = FilterOut<string | number | boolean, string | boolean>;
// Numbers: numberFor complex transformations, break them into smaller steps:
// Multi-step type transformation
type Step1<T> = [T] extends [never] ? never : T;
type Step2<T> = Step1<T> extends string ? string[] : Step1<T>;
type Final<T> = Step2<T>;
type Result = Final<string>; // string[]TypeScript's built-in utilities handle never correctly. Use them instead of custom implementations:
// Instead of custom filters, use built-ins
type OnlyStrings = Extract<string | number | boolean, string>;
// OnlyStrings: string
type NotStrings = Exclude<string | number | boolean, string>;
// NotStrings: number | boolean
type NoNulls = NonNullable<string | null | undefined>;
// NoNulls: string
// Combining utilities
type SafePick<T, K extends keyof T> = {
[P in K]: NonNullable<T[P]>;
};
interface User {
name: string;
email: string | null;
age: number | undefined;
}
type RequiredUser = SafePick<User, "name" | "email">;
// { name: string; email: string }These utilities are battle-tested and handle edge cases with never automatically.
If the error occurs at the constraint level, modify your generic constraints:
// WRONG - Constraint may include never
function process<T extends string | never>(value: T): void {
// Error: Cannot extend with union type containing 'never'
}
// CORRECT - Remove never from constraint
function process<T extends string>(value: T): void {
// Works correctly
}
// Or handle it explicitly
function process<T>(value: T extends never ? string : T): void {
// Explicit never handling
}
// For conditional constraints
type ValidKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
// Use with an object type
interface Config {
name: string;
getValue: () => string;
timeout: number;
}
type DataKeys = ValidKeys<Config>;
// DataKeys: "name" | "timeout" (excludes getValue)### Understanding Distributive Conditional Types
Distributive conditional types are a key feature of TypeScript's type system. They work differently depending on whether the checked type is a "naked type parameter":
Distributive (naked type parameter):
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// Distributes: (string extends any ? string[] : never) | (number extends any ? number[] : never)
// Result: string[] | number[]Non-distributive (wrapped type parameter):
type ToArray<T> = [T] extends [any] ? T[] : never;
type Result = ToArray<string | number>;
// No distribution: checks (string | number) as whole
// Result: (string | number)[]### The Never Type's Special Behavior
The 'never' type has unique properties in TypeScript:
1. Union elimination: string | never simplifies to string
2. Intersection dominance: string & never results in never
3. Empty set: Represents the type of values that never occur
4. Bottom type: Subtype of every other type
When 'never' appears in conditional types:
// never distributes to never
type Dist<T> = T extends any ? T[] : never;
type Result = Dist<never>; // never (not never[])
// This is because distributive types distribute over never's zero members
// For each member in never (none), apply the transformation = never### Common Patterns and Solutions
Pattern 1: Type filtering with never
// Filter out types from a union
type Filter<T, U> = T extends U ? never : T;
type NonString = Filter<string | number | boolean, string>;
// NonString: number | booleanPattern 2: Detecting never
// Standard approach (doesn't work with distribution)
type IsNever<T> = T extends never ? true : false;
type Test = IsNever<never>; // never (not true!)
// Correct approach (prevents distribution)
type IsNever<T> = [T] extends [never] ? true : false;
type Test = IsNever<never>; // true ✓Pattern 3: Exhaustiveness checking
type Status = "pending" | "approved" | "rejected";
function handleStatus(status: Status): string {
switch (status) {
case "pending":
return "Waiting";
case "approved":
return "Done";
case "rejected":
return "Failed";
default:
// If Status gains new member, this will error
const _exhaustive: never = status;
return _exhaustive;
}
}### NoInfer and Union Distribution
TypeScript 5.4+ introduced the NoInfer utility type which prevents type inference in certain contexts. Be aware that distributive conditional types can't distribute over NoInfer types wrapping a union:
type Dist<T> = T extends string ? true : false;
// This works
type Test1 = Dist<string | number>; // boolean
// This may cause issues in complex scenarios
type Test2<T> = Dist<NoInfer<T>>;### Debugging Type Errors
When encountering this error, use these techniques to debug:
1. Hover inspection: Hover over types in your IDE to see what they resolve to
2. Type assertions: Use helper types to inspect intermediate results:
type Inspect<T> = T extends infer U ? { type: U } : never;
type Result = Inspect<YourComplexType>;3. Simplify gradually: Break complex conditional types into smaller, testable pieces
4. Use TypeScript playground: Test isolated type transformations at [typescriptlang.org/play](https://www.typescriptlang.org/play)
### When to Avoid Conditional Types
Sometimes simpler alternatives exist:
// Instead of complex conditional types
type Complex<T> = T extends string
? string[]
: T extends number
? number[]
: never;
// Consider overloading or union types
type Simple = string[] | number[];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