A generic declares type parameters (<T>) that callers fill in, usually by inference. It exists to keep a relationship between types that any or a union would throw away: identity<T>(x: T): T says the result has the same type as the argument.
Generics apply to functions, interfaces, type aliases and classes. Per the handbook, classes are generic only over their instance side: static members cannot use the class's type parameter.
Constraints. Inside a body, an unconstrained T is opaque, so you can only do things valid for every type. T extends HasLength says "any type assignable to HasLength", and the body may use .length. extends here means assignable to, not inheritance. A constraint does two things:
- It restricts the call: arguments that do not satisfy it are errors at the call site.
- It gives the body something to work with:
Tis treated as its constraint for property access.
The result is still T, not the constraint. longest("ab", "c") returns "ab" | "c", not HasLength. That is the reason to write <T extends X>(a: T): T rather than (a: X): X.
A constraint can refer to another type parameter. The classic form is K extends keyof T, which lets pick(obj, "name") accept only real keys and return T[K].
Defaults. <T = unknown> makes the argument optional when it cannot be inferred or written. Required parameters cannot follow optional ones (error TS2706).
Where it bites. The constraint is a lower bound for the body but a caller can pick any subtype. In function f<T extends string>(): T { return "x"; }, "x" is not assignable to T, because T could be "y". Reach for generics only when a type parameter appears at least twice (relates inputs to outputs); a parameter used once is usually just its constraint.