Lesson 48 of 80
T gets filled in from the values you actually pass — but only from parameters T appears in, only left to right, and not always with the answer you expected.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The generics lesson covers T as a placeholder for a type, filled in at the call site. This lesson is about how TypeScript works out what to fill it with when you don't say so explicitly — and the couple of places that process gives a less useful answer than it looks like it should.
function firstOf<T>(arr: T[]): T | undefined {
return arr[0];
}
const a = firstOf([1, 2, 3]); // T inferred as number, from the array's element type
const b = firstOf(["a", "b"]); // T inferred as string
const c = firstOf([]); // T inferred as unknown — nothing in the argument to learn from
Inference only has one place to look: the actual argument values, matched up against where T appears in the parameter list. An empty array carries no element to inspect, so there's nothing to infer T from, and it falls back to unknown.
function mapArray<T, U>(arr: T[], fn: (item: T) => U): U[] {
return arr.map(fn);
}
mapArray([1, 2, 3], (n) => n.toFixed(2));
// T is inferred as number from `arr` FIRST — then `fn`'s parameter `n`
// is contextually typed as number because T is already pinned down.
// n.toFixed(2) type-checks; n is not `any`.
TypeScript infers generic type parameters by walking the parameter list in order, not by scanning every parameter independently and merging results. By the time it reaches fn, T is already settled from arr, so the callback's parameter gets a real, useful type instead of falling back to an implicit any. This is exactly why array-then-callback is the standard shape for generic helpers like this one — reverse the order and the callback parameter would have nothing to be inferred from yet.
function pair<T>(a: T, b: T): [T, T] {
return [a, b];
}
const p = pair(1, "two");
// T inferred as `string | number` — not an error, just a wider T than either call site probably wanted
When two parameters both reference the same T and the arguments disagree, TypeScript doesn't reject the call — it infers the union of every candidate, silently widening T to something looser than either argument alone.
T that only appears in the return type — never in any parameter — can't be inferred from arguments at all. There's nothing in the call to look at. Calling that function without an explicit type argument leaves T at unknown or its default, regardless of what the return value is later assigned to.pair(1, "two") case above compiles cleanly with T as string | number — this is one of the quieter ways generic code ends up looser than the author intended, with nothing flagging it.T before the parameter that "consumes" T (array before callback, not callback before array) is what lets later parameters get properly typed instead of implicitly widened.firstOf<string>([1, 2, 3]) compiles even though the array is clearly numbers, because you've told TypeScript what to use directly; inference only runs to fill in what you didn't specify."Why does the callback parameter in mapArray get a real type instead of any?" tests whether someone understands generic inference as a left-to-right, order-dependent process — not a single pass that magically figures out every parameter's type from the whole signature at once.