Complex constraints use multiple extends, conditional types, and recursive patterns. Variance determines how type parameters relate in subtyping: covariant (out), contravariant (in), invariant (both), and bivariant (neither). Understanding variance helps design safe generic APIs.
Complex Constraints:
T extends A & BT extends C ? D : ET extends { prop: T }Variance:
T in output position, subtype preservingT in input position, subtype reversingT in both positions, exact match requiredPractical Implications:
// Multiple constraints
interface HasId { id: number; }
interface HasName { name: string; }
function merge<T extends HasId & HasName>(item: T) {
return { ...item, displayName: `${item.name} (${item.id})` };
}
// Constraint referencing other type parameter
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Conditional type in constraint
type ArrayElement<T> = T extends Array<infer E> ? E : never;
function first<T extends any[]>(arr: T): ArrayElement<T> {
return arr[0];
}
const n = first([1, 2, 3]); // number
const s = first(['a', 'b']); // string
// Recursive constraint (JSON-like values)
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
function toJSON<T extends JSONValue>(value: T): string {
return JSON.stringify(value);
}