Generics, Constraints and Inference
Generics let one function or type work across many types while keeping the relationship between inputs and outputs. Know how type arguments are inferred, how constraints and defaults shape them, and the handful of rules that produce surprising errors.
Key points
- 1
A type parameter should relate at least two places, such as an argument and the return type. One that appears only in the return type (
parse<T>(s): T) is an unchecked cast chosen by the caller. - 2
Inference collects candidates from the arguments, left to right. Context-sensitive callbacks (unannotated parameters) are deferred, and since TS 4.7 members of one object literal are processed in source order.
- 3
Literal inferences are kept when T is the whole return type or has a primitive constraint; otherwise they widen.
<const T>(TS 5.0) asks foras const-style inference of literals written in the call. - 4
Constraints (
T extends { length: number },K extends keyof T) are what let you use members inside the body. An unconstrained T,extends any,extends objectandextends {}guarantee no properties. - 5
There is no partial inference: once you pass type arguments, every required one must be supplied. Defaults (
<T, U = T[]>) and curried helpers work around this.NoInfer<T>(TS 5.4) keeps a parameter out of inference. - 6
Instantiation expressions (TS 4.7) apply type arguments without calling:
const ErrorMap = Map<string, Error>. Higher-order inference (TS 3.4) keeps type parameters when generic functions are composed. - 7
in/outvariance annotations (TS 4.7) are checked against the structure and can speed up checking, but they never make an unsound assignment legal.
Common traps
Returning a concrete object from
<T extends Base>(): Tfails with 'could be instantiated with a different subtype': the caller may choose a T with more properties.new Stack()with no constructor argument givesStack<unknown>, which silently accepts mixed values.Static class members can't use the class's type parameter, and method-shorthand parameters are bivariant, so they escape strictFunctionTypes and
outchecks.
Read the source
Test yourself on Generics, Constraints and Inference
Ten questions, with the answer and explanation after each one.