This error occurs when a generic type parameter has constraints that contradict each other or when trying to use a constrained type in ways that conflict with its defined boundaries.
This error appears when TypeScript detects that a generic type parameter's constraints are incompatible or contradictory. The most common manifestation is the error message "'X' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Y'." The core issue stems from TypeScript's type safety guarantees with generics. When you constrain a type parameter with `T extends SomeType`, TypeScript knows T must be at least SomeType, but it could be a more specific subtype with additional properties or stricter requirements. If you try to assign a value that only satisfies the base constraint, TypeScript cannot guarantee it will satisfy all possible subtypes of T. This is a design limitation in TypeScript's type system intended to prevent runtime errors. While the constraint establishes a minimum type requirement, the actual type parameter could be instantiated with a more specific type that has additional requirements beyond the constraint.
Examine the full error message to understand which constraint is being violated:
// Error example
function createDefault<T extends string>(value?: T): T {
return value ?? 'default'; // Error: 'default' is string, but T could be a literal type
}The error indicates that while 'default' satisfies string, the type parameter T might be instantiated with a more specific subtype like 'specific-value'.
When using constrained generics, avoid default values that only satisfy the base constraint:
// Before (error)
function process<T extends string>(value: T = 'default'): T {
return value;
}
// After (fixed) - require the value
function process<T extends string>(value: T): T {
return value;
}
// Alternative - use overloads
function process<T extends string>(value: T): T;
function process(value?: string): string;
function process(value: string = 'default'): string {
return value;
}If you're certain about the type being used, you can use type assertions:
function createDefault<T extends string>(): T {
return 'default' as T; // Assertion acknowledges the limitation
}
// Or create a factory pattern
function createDefault<T extends string>(factory: () => T): T {
return factory();
}
const result = createDefault(() => 'specific-value' as const);Warning: Type assertions bypass type safety. Use them only when you're certain the type is correct.
Instead of returning the generic type, return the constraint type when appropriate:
// Before (error)
function getDefault<T extends { id: number }>(value?: T): T {
return value ?? { id: 0 }; // Error
}
// After (fixed) - return constraint type
function getDefault<T extends { id: number }>(value?: T): { id: number } {
return value ?? { id: 0 }; // Works
}
// Or use a required parameter that creates T
function getDefault<T extends { id: number }>(
value: T | undefined,
factory: () => T
): T {
return value ?? factory();
}When multiple constraints conflict, combine them into a single intersection type:
// Before (potential conflict)
interface A { a: string }
interface B { b: number }
function process<T extends A, T extends B>(value: T) { } // Invalid syntax
// After (fixed)
function process<T extends A & B>(value: T) {
// T must satisfy both A and B
}
// For union constraints
function process<T extends A | B>(value: T) {
// T must satisfy either A or B
}When one type parameter depends on another, ensure proper ordering:
// Before (error)
function getProp<K extends keyof T, T extends object>(obj: T, key: K) { } // Error: T used before declaration
// After (fixed)
function getProp<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}Type parameters are evaluated left-to-right, so dependent constraints must reference earlier parameters.
Design Limitation: This error often represents a fundamental limitation in TypeScript's structural type system. When you write T extends SomeType, you're establishing a minimum contract, but T could be instantiated with any type that satisfies and extends that contract. This means you cannot safely create "default" values of type T because you don't know what additional properties or constraints the actual type might have.
Union Constraints and Intersection Behavior: A known issue (TypeScript #60892) exists where union constraints can unexpectedly become intersections in certain contexts. If you declare T extends A | B, the compiler may treat it as T extends A & B in some error messages, requiring values to satisfy both types simultaneously.
Workarounds for Libraries: Library authors dealing with this limitation often use:
- Factory patterns that require callers to provide the exact type
- Function overloads to separate constrained and unconstrained cases
- Conditional types to derive safe return types from constraints
- Branded types or discriminated unions to make subtypes more explicit
Type Inference Issues: When TypeScript infers type parameters automatically, it may infer overly broad types (like unknown or string) that don't satisfy more specific constraints declared elsewhere in the signature. Explicitly providing type arguments can resolve these inference conflicts: myFunction<SpecificType>(value) instead of relying on inference.
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 'X' is not assignable to inferred conditional type
How to fix "Type not assignable to inferred conditional" in TypeScript