In TypeScript, why write `function get<T, K extends keyof T>(obj: T, key: K): T[K]` instead of the simpler `function get<T>(obj: T, key: keyof T): T[keyof T]`?
answer
- one parameter forgets, the other remembers
- which key versus some key
- union of every property type
- inference captures the literal
- precision only helps on reads
basics
~20 sGiving the key its own type parameter captures which key was passed. With key: keyof T the compiler only knows it was some key, so the return type T[keyof T] is the union of every property type, and the caller must narrow it back down.
solid answer
~40 sBoth signatures reject invalid keys, so the difference is entirely in the result. `key: keyof T` is a plain parameter of a union type: the checker knows the argument is *one of* the keys but not *which one*, so the best return type it can state is `T[keyof T]`, the union of all property types. Promoting the key to its own type parameter `K extends keyof T` makes inference capture the specific literal at each call site, so `T[K]` collapses to exactly that property's type. For `{ id: number; name: string }` the first version returns `string | number` everywhere and every caller has to narrow; the second returns `number` for `'id'` and `string` for `'name'`. The general lesson is that a type parameter *remembers* what was passed, while a union-typed parameter forgets.
code
typescript · 9 linesconst user = { id: 1, name: "ada" };
declare function getUnion<T>(o: T, k: keyof T): T[keyof T];
declare function getGeneric<T, K extends keyof T>(o: T, k: K): T[K];
const a = getUnion(user, "name"); // string | number
const b = getGeneric(user, "name"); // string
const upper: string = b.toUpperCase();go deeper
Know that keyof T restricts which key strings are allowed, and that indexing a type with a union of keys gives a union of value types back.
Explain that a type parameter is inferred per call and therefore remembers the specific literal, while a union-typed parameter is only checked against and forgets. Give the string | number versus string contrast.
Show the write-side consequence: relating the value parameter to the same K is what prevents a value valid for one property being stored under another. Also note precision degrades gracefully when the key is a union variable.
Weigh API ergonomics: precise key-indexed signatures make call sites self-checking but couple consumers to the exact object shape and produce long inferred types in editors and .d.ts output. Decide where in a library that tradeoff pays.
## Two signatures that accept the same calls ```ts declare function getA<T>(obj: T, key: keyof T): T[keyof T]; declare function getB<T, K extends keyof T>(obj: T, key: K): T[K]; ``` Both reject `getX(user, 'nope')`. The difference shows up on the way out. ## What a union-typed parameter forgets In `getA`, `key` is an ordinary parameter whose type is `keyof T` — for `{ id: number; name: string }` that is `"id" | "name"`. The signature is a promise about *every* call: the argument is some member of that union. Nothing in the signature can refer to the particular member supplied, so the only honest return type is `T[keyof T]`, i.e. `T["id"] | T["name"]` = `number | string`. ```ts const user = { id: 1, name: "ada" }; const v = getA(user, "name"); // string | number v.toUpperCase(); // error: number has no toUpperCase ``` The caller is pushed into `typeof v === "string"` checks or an assertion — reintroducing exactly the imprecision the annotation was supposed to remove. ## What a type parameter remembers In `getB`, the key argument drives *inference* rather than merely being checked against a union. Because `K`'s constraint is a union of string literal types, TypeScript preserves the literal instead of widening to `string`, so `K` becomes `"name"` for that call. The return type `T[K]` is then evaluated with that binding and yields `string`. ```ts const w = getB(user, "name"); // string w.toUpperCase(); // fine ``` This is the general shape of *relating* type parameters: introduce a parameter for the thing that varies per call, constrain it to the other parameter, and express the result in terms of both. ## Precision is not free — it is only as good as the argument A type parameter captures whatever it is given. Pass a union-typed variable and `K` infers that union, and `T[K]` distributes back into a union: ```ts declare const k: "id" | "name"; const x = getB(user, k); // number | string, same as getA ``` So `getB` is never *worse* than `getA`, and is strictly better whenever the caller knows the key concretely — which is the overwhelmingly common case, since keys are usually written as literals in source. ## When you genuinely want the union form If the function's contract really is "any key, and I will treat the value uniformly", the union parameter is simpler and states that honestly. Two examples: a logger that stringifies whatever it reads, or a function that takes a `keyof T` to use as a sort field and never returns the value at all. Adding a type parameter you do not use in the result buys nothing. ## Why this also matters for writes The read case is about precision; the write case is about safety. With `value: T[keyof T]`, a setter would accept a value valid for *any* property under *any* key — a string could be written into a numeric field with no complaint. Relating the value to the same `K` (`value: T[K]`) is what ties the two arguments together. ## Interview framing Say it as one sentence: a union-typed parameter tells you the key is one of many, a type parameter tells you which one. Then give the concrete consequence — `string | number` versus `string` — and note that the extra parameter costs nothing at runtime, since all of it is erased.
- Is there any runtime cost to introducing the second type parameter?None. Type parameters are erased at compile time — the emitted JavaScript for both signatures is the same function performing `obj[key]`. Generics exist only for the checker, so the choice is purely about how precise the call sites are, never about speed or bundle size.
- When would you deliberately prefer the `key: keyof T` form?When the specific key genuinely does not affect the result: a function that takes a key to sort or group by and never returns the value, or one that stringifies whatever it reads. Adding a type parameter that appears only once in the signature buys no precision and makes the declaration harder to read.
- If both signatures reject bad keys, what exactly is the extra parameter buying?Only the result type. Key validation comes from the constraint `keyof T`, which both forms use. The type parameter adds the ability to *name* the key in the return type, so `T[K]` can resolve to one property instead of the union `T[keyof T]`. That saves every caller a narrowing step.
saying these in an interview costs you the question
- Thinks key: keyof T lets the caller pass any string
- Says both forms return the same type
- Assumes the extra type parameter adds runtime overhead
- Believes narrowing the result afterwards is just as good
- Claims T[keyof T] resolves to the key that was passed