Compare these two TypeScript signatures: `function getFirst<T>(arr: T[]): T` and `function getLength<T>(arr: T[]): number`. Why does the type parameter earn its place in only one of them, and how would you rewrite the other?
answer
- count the mentions of T
- a type parameter is a relationship
- used once is not polymorphism
- the return type never depends on it
- unknown[] does the same job
basics
~20 sA type parameter earns its place only when it links two positions in a signature. getFirst returns T, so it carries the element type back to the caller; getLength never uses T again, so it should just take unknown[].
solid answer
~50 sMy test is whether the type parameter appears in at least two places in the signature, because that is what makes it a relationship rather than decoration. In `getFirst<T>(arr: T[]): T` it appears twice: T is inferred from the argument and flows into the return type, so calling it with a `string[]` gives back `string` instead of something vague. In `getLength<T>(arr: T[]): number` T is mentioned once. Nothing downstream depends on it, so it is inferred and thrown away, and the caller gains nothing over a plain parameter type. I would rewrite it as `getLength(arr: readonly unknown[]): number` — same accepted arguments, one fewer knob in the API, and a signature that reads honestly. A type parameter used once is either dead weight or, when the one use is the return type, a hidden cast.
go deeper
Be ready to point at a signature and say how many times the type parameter is mentioned, and to state plainly that one mention means the generic is not doing anything.
Explain the mechanics: the parameter position is the inference site, the return type is the reader, and with no reader the inferred type is discarded. Show the concrete rewrite using the constraint or unknown.
Show that you police this in review. Explain why the single-use parameter in return position is worse than merely useless, and how you would widen such an API without breaking every call site at once.
Own the API-design tradeoff: every exported type parameter is a public knob you must keep working across versions. Argue for pushing generics down into implementation details and keeping published signatures as concrete as the callers allow.
## What a type parameter is actually for A generic type parameter is a **variable that ranges over types**, and like any variable it is only useful if something reads it. In a function signature the readers are the other positions: another parameter, the return type, a nested type argument, or a constraint on a second parameter. When T is written in exactly one position, nothing can read it, so the compiler infers a value for it and immediately discards it. The signature is longer, the quick-info popup is noisier, and the checking behaviour is identical to the version without T. That gives the review rule this topic is named after: **count the mentions of T. One mention means the generic does nothing.** ## Why getFirst needs T ```ts function getFirst<T>(arr: T[]): T { return arr[0]; } const n = getFirst([1, 2, 3]); // n: number const s = getFirst(["a", "b"]); // s: string ``` Here T appears in the parameter type `T[]` **and** in the return type. The parameter is the *inference site*: the compiler looks at the argument, decides T is `number`, and then substitutes that into the return type. The type parameter is doing the only job generics have — transporting a type the caller supplied from one part of the signature to another. Without it you would have to write `getFirst(arr: unknown[]): unknown` and every caller would need a cast. ## Why getLength does not ```ts function getLength<T>(arr: T[]): number { return arr.length; } ``` T is inferred (as `number`, `string`, whatever) and then never mentioned again. The return type is always `number`. The body cannot use T either, because types are erased at compile time — there is no `T` value to inspect at runtime. So the generic version and the non-generic version accept exactly the same arguments and return exactly the same type. The honest signature is: ```ts function getLength(arr: readonly unknown[]): number { return arr.length; } ``` `readonly unknown[]` is a slightly better choice than `unknown[]`: `unknown` accepts any element type, and `readonly` documents that the function does not mutate the array (and lets callers pass a `readonly` array or a tuple). Note that `any[]` would *also* compile, but `any` disables checking inside the function body, which `unknown` does not. ## The same smell in three costumes 1. **Completely unused.** `function log<T>(msg: string): void` — T is not even in the parameter list. Callers can pass any type argument they like and it is ignored. 2. **Parameter-position only.** The `getLength` case above, and its constrained cousin `function save<T extends Entity>(e: T): void`, which behaves identically to `function save(e: Entity): void` for every caller. 3. **Return-position only.** `function parse<T>(json: string): T`. This is the dangerous one: because there is no inference site, the *caller* chooses T, and the compiler simply believes them. That is an unchecked assertion wearing a generic costume, not polymorphism. Case 3 is the one interviewers push on, because the first two are merely noise while the third actively lies about the value's type. ## Why the compiler will not tell you Nothing in the type system requires a type parameter to be useful, so all three forms type-check. Detection is a lint concern rather than a compiler one; typescript-eslint ships a rule, `@typescript-eslint/no-unnecessary-type-parameters`, that reports type parameters used only once. In review the manual check is faster than it sounds: read the signature, ignore the body, and ask whether removing `<T>` and replacing every `T` with the constraint (or `unknown`) would change what any caller can do. ## The rewrite decision When you find a single-use parameter, the replacement follows from where it appeared: - Used only as a parameter type with no constraint → use `unknown` (or `readonly unknown[]` for arrays). - Used only as a parameter type with a constraint → use the constraint itself as the parameter type. - Used only in the return type → do **not** simply swap in the constraint; the function is returning something it never checked. Return `unknown` and make the caller narrow, or add a real runtime source for the type such as a validator parameter, which also gives T a second appearance. ## What this signals in an interview Reaching for a generic is easy; declining to is the maturity signal. Generics exist to preserve information the caller already has, not to make a function "more reusable" in the abstract. If no caller-supplied type reaches any output of the function, the simplest type that accepts the input is the better API — shorter to read, easier to change later, and impossible to misuse by passing a type argument that means nothing.
- Does adding `<T>` ever cost anything at runtime?No. Type parameters are erased during compilation, so the emitted JavaScript for the generic and non-generic versions is identical. The cost is entirely at design time: a longer signature, a noisier hover, more surface area to change, and the risk that someone passes an explicit type argument believing it does something.
- What about `function save<T extends Entity>(e: T): void` — the parameter is constrained, so surely the generic is doing work?The constraint does the work, not the type parameter. That signature accepts exactly the arguments `function save(e: Entity): void` accepts, so the generic still appears once. The constrained form only becomes meaningful when T shows up elsewhere too, for instance `save<T extends Entity>(e: T): T` or a second parameter typed `keyof T`.
- Are there legitimate signatures where the type parameter appears twice but is still useless?Yes — if both appearances are in the same argument slot with no output dependency, for instance `function pair<T>(a: T, b: T): void`. It does constrain the two arguments to agree, which is real work, so I would keep it. But `function pair<T>(a: T, b: T): boolean` where the body only compares by identity buys you a compile-time pairing rule you may or may not actually want.
saying these in an interview costs you the question
- Generics always make a function more reusable, so add one
- Using unknown[] instead of T[] would lose type safety
- A generic is how you avoid writing any
- The compiler reports an error for a type parameter you never use
- The type parameter is checked at runtime when the function is called