"Constraints are how you say 'whatever T is, it must at least have these properties.'"
The constraint syntax
<T extends Constraint> requires T to be assignable to the constraint. This is structural assignment — T must have at least the members of Constraint (with compatible types). Once constrained, you can access those members inside the function/type body, because the compiler knows they exist.
Without a constraint, T is unknown-like — you can pass it around, but you can't access any properties (the compiler doesn't know what T has). Constraints unlock access.
The canonical examples
- Has length:
function len<T extends { length: number }>(x: T): number— works on strings, arrays, anything with alengthfield. - Has key:
function get<T, K extends keyof T>(obj: T, key: K): T[K]— K is constrained to be a key of T, return is the value at that key. - Is a class:
function instantiate<T>(Ctor: new () => T): T— T is whatever the constructor produces.
Constraints + inference
The compiler considers constraints when inferring T. If you constrain T extends string, the inference can't pick number. The constraint narrows the legal space of T. This often makes function calls more precise.
If you find yourself accessing a property on a generic T and the compiler complains, you need a constraint. The constraint tells the compiler what T is guaranteed to have, so the property access is safe.