In TypeScript, given `declare function first<T>(items: T[]): T | undefined`, what is the type of `first([1, 2, 3])`, and where did the compiler get `T` from?
answer
- the compiler reads the arguments
- parameter positions are inference sites
- number[] matched against T[]
- return type consumes T, never supplies it
- an empty array literal offers nothing
basics
~20 sfirst([1, 2, 3]) has type number | undefined. TypeScript matches the argument type number[] against the parameter type T[] at the call site, infers T as number, and substitutes that into the declared return type.
solid answer
~50 s`first([1, 2, 3])` is `number | undefined`. Every parameter position where a type parameter appears is an *inference site*: the compiler works out each argument's own type, matches it structurally against the declared parameter type, and whatever lines up opposite `T` becomes a candidate. Here the array literal is `number[]`, matching it against `T[]` puts `number` opposite `T`, so `T` is fixed to `number` and the return type `T | undefined` becomes `number | undefined`. That is why you almost never write `first<number>([1, 2, 3])` — the explicit type argument is only needed when nothing at the call pins `T` down. Two results worth knowing: `first([])` is just `undefined`, because an empty array literal is `never[]`, and `first([1, "x"])` is `string | number | undefined`, because the literal's element type is already a union before inference runs.
code
typescript · 7 linesdeclare function first<T>(items: T[]): T | undefined;
export const a = first([1, 2, 3]); // number | undefined
export const b = first(["x"]); // string | undefined
export const c = first([1, "x"]); // string | number | undefined
export const d = first([]); // undefined — T infers as never
export const e = first<number>([1, 2]); // explicit type argument, same resultgo deeper
Be ready to read a generic signature aloud and say which argument gave the compiler its type. Knowing that first([1, 2, 3]) is number | undefined and that you did not have to write <number> is the expected answer.
Explain the mechanics: argument types are computed first, matched structurally against the declared parameter types, and each match contributes a candidate. Be able to say why the return position contributes nothing.
Show you can debug from this. When a colleague reports a surprising inferred type, trace it back to the argument that supplied the candidate — the empty-array-to-never case is the classic one to spot in review.
Own the API-shape consequence: a signature whose type parameters have no argument-side inference site pushes work onto every caller. Judge new generic helpers by whether a normal call infers correctly with zero annotations.
## The two halves of a generic call A generic function declares one or more **type parameters** — placeholders in angle brackets that stand for a type the caller decides. `declare function first<T>(items: T[]): T | undefined` declares one, `T`, and then uses it twice: in the parameter type `T[]` and in the return type `T | undefined`. The declaration never says what `T` is. The caller decides, and in ordinary code the caller decides *implicitly*, just by passing an argument. Working out `T` from the arguments is called **type argument inference**. ## Inference sites An **inference site** is a place in the parameter list where a type parameter appears. When you write a call, the compiler: 1. Works out the type of each argument on its own, before looking at the signature. 2. Matches that type structurally against the declared parameter type. 3. Wherever a type parameter appears on the parameter side, whatever sits opposite it on the argument side becomes a **candidate** for that parameter. 4. Fixes the type parameter to a candidate, then substitutes it everywhere in the signature — including the return type. Walking `first([1, 2, 3])` through those steps: the array literal's own type is `number[]`; matching `number[]` against `T[]` lines `number` up opposite `T`; `number` is the only candidate, so `T` is `number`; the call's type is therefore `number | undefined`. ```ts declare function first<T>(items: T[]): T | undefined; const value = first([1, 2, 3]); // ^? number | undefined ``` Notice that the return type did no work. It consumed `T`; it did not supply it. A type parameter that appears **only** in the return type has no inference site at all, and the compiler has to fall back to something else. ## The argument's own type is decided first Inference never reaches inside the argument to redecide it. Whatever the argument's type already is, that is what gets matched: ```ts first(["x"]); // string | undefined first([1, "x"]); // string | number | undefined — the literal is (string | number)[] first([]); // undefined — an empty array literal is never[], so T is never ``` The empty-array case is the one that surprises people. `first([])` is not an error and does not produce `any`; the element type of `[]` is `never`, so `T` is `never` and `T | undefined` collapses to `undefined`. In real code this shows up when an accumulator starts empty. Annotate the variable — `const rows: Row[] = []` — and the call infers `Row` as intended. ## The call's context can also supply a candidate Arguments are the main source, but not the only one. If a call's result flows into a position with a known expected type, that expected type can supply a candidate too — with lower priority than arguments: ```ts declare function make<T>(): T; const name: string = make(); // T is string, taken from the annotation ``` Because argument candidates outrank the contextual one, an annotation cannot override what the arguments already decided. `const w: string | number = pick(1)` still infers `number` for a `pick<T>(v: T): T`. ## Writing the type argument yourself Explicit type arguments are always available as an escape hatch: `first<number>([1, 2, 3])`. Supplying them is all-or-nothing — you must give one for every type parameter unless the trailing ones have defaults, so a two-parameter generic called with a single type argument is rejected. In practice you reach for explicit type arguments only when the call has no usable inference site, or when inference lands somewhere you did not want. ## None of this exists at runtime Types are erased. `first<T>` compiles to an ordinary JavaScript function with no `T` anywhere; inference is a compile-time bookkeeping exercise that produces better editor types and better errors, and costs nothing when the program runs. A function cannot branch on `T`, and no `T` is available to inspect. ## Common misreadings Inference is not a runtime check, it is not driven by the variable name you assign to, and it does not fall back to `any` when it is unsure. It is a structural match between the argument types you actually passed and the parameter types the signature declares — and once you can narrate that match, most "why is this type weird?" questions answer themselves.
- What does `T` become when the argument is an empty array literal, and why does that bite in real code?An empty array literal has type `never[]`, so `T` infers as `never` — `first([])` is just `undefined`. It bites when an accumulator starts out empty and everything downstream ends up typed `never`. The fix is to give the variable a type where it is declared, `const rows: Row[] = []`, or to pass an explicit type argument at the call.
- Does the type parameter's appearance in the return type help the compiler work out `T`?No. The return position consumes `T`; it never supplies a candidate. A type parameter that appears only in the return type has no inference site among the arguments, so it falls back to its constraint or to `unknown` unless the caller writes the type argument. What can help is the call's contextual type — an annotated variable receiving the result — but that has lower priority than any argument candidate.
- Is there any runtime cost to a generic call?None. Type parameters are erased during compilation, so the emitted JavaScript is the same function with the same arguments — there is no reified `T`, no check, and no metadata. That also means a generic function cannot inspect or branch on `T` at runtime; if behaviour must depend on the type, you have to pass a real value, such as a discriminant field or a parser.
saying these in an interview costs you the question
- Claims T is looked up at runtime from the value
- Says inference falls back to any when unsure
- Thinks the return type position drives inference
- Believes you must always write the type argument
- Assumes first([]) is an error or gives any