TypeScript Generic Constraints in Depth
A constraint limits which types a type parameter accepts. Constraints combine, so one parameter can demand several shapes at once or one shape from a short list.
An intersection constraint demands both shapes
The constraint HasId & HasName requires an id and a name in the same argument. Extra members ride along untouched.
interface HasId {
id: number;
}
interface HasName {
name: string;
}
function label<T extends HasId & HasName>(item: T): string {
return item.id + ': ' + item.name;
}
console.log(label({ id: 1, name: 'Pen' }));
console.log(label({ id: 2, name: 'Ruler', stock: 7 }));A union constraint accepts either shape
The constraint Circle | Square accepts a value matching one of the two shapes. The shared kind member is readable on both.
interface Circle {
kind: 'circle';
radius: number;
}
interface Square {
kind: 'square';
side: number;
}
function kindOf<T extends Circle | Square>(shape: T): string {
return shape.kind;
}
console.log(kindOf({ kind: 'circle', radius: 3 }));
console.log(kindOf({ kind: 'square', side: 5 }));A missing member breaks the constraint
The argument below has a name but no id, so it fails the intersection constraint. The compiler names the missing member.
interface HasId {
id: number;
}
interface HasName {
name: string;
}
function label<T extends HasId & HasName>(item: T): string {
return item.id + ': ' + item.name;
}
console.log(label({ name: 'Pen' }));Example
interface StockItem {
id: number;
stock: number;
}
function restock<T extends StockItem>(item: T, extra: number): T {
return { ...item, stock: item.stock + extra };
}
const pen = { id: 1, stock: 4, name: 'Pen' };
const updated = restock(pen, 6);
console.log(updated.name);
console.log(updated.stock);Key points
- An intersection constraint with & demands every listed shape in one argument.
- A union constraint accepts a value that matches any one of the listed shapes.
- The argument keeps its own extra members when it passes through a variable.
- A generic return type T preserves the exact argument type for the caller.
- An argument missing a required member fails the constraint and the compile.
In this example, restock returns T itself, so the caller keeps the name member that the constraint never mentioned. The constraint sets a floor for the argument without flattening its type.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท TypeScript Playground
Exercise
What does the constraint T extends HasId & HasName require?