For f<T>(...), the checker walks each argument against its parameter type and records candidates for T wherever T appears. It then resolves them:
- Covariant positions (values going in, like
a: T,T[]): the handbook's best common type rule applies: pick the candidate compatible with the others. When none fits, the observed behaviour is that the first candidate wins and later arguments are checked against it:same(1, "a")reports"a"is not assignable to1, andsame("a", 1)reports1is not assignable to"a"(both checked). - Contravariant positions (callback parameters,
(x: T) => void): since TS 2.6 the result is the best common subtype, soboth(onAnimal, onDog)infersDog. - Literal widening. An unconstrained
Tfrom"a"and"b"becomesstring. A constraint such asT extends stringkeeps"a" | "b". - Return-type context. The expected type is also a source:
const x: string = parse("{}")infersT = string. - No source. If no argument mentions
T, you get the constraint, orunknownwhen there is none. An empty array literal givesnever.
When it goes wrong: the argument you intended as the source of truth is one of several candidates (the NoInfer case: a default value widens the union and a wrong value is accepted); a type parameter appears only in the return type, so callers get unknown; or you wrote one type argument and the compiler demands the rest.
Explicit arguments are all or nothing. TypeScript has no partial inference: two<string>("a", 1) is error TS2558. Parameters with defaults may be omitted from the right, and then the default is used, not inferred. Fixes: supply every argument, or give the trailing parameters defaults.
Use NoInfer to say which position must not contribute candidates.