๐ŸŽ“ A1Learner
๐Ÿ”
0 of 42 lessons completed

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?