BeCuriouzLearn something new every day
Practical Generics: Utilities That Work With Any Type
💡 Add a type parameter when a utility's return type must track its argument type; use extends constraints to safely work with the value while staying generic.
You've seen how a type parameter like lets a function remember the type it was handed. Now let's use that to write utilities you actually reuse instead of copy-pasting one version per type.
Start with the classic: identity is trivial, but first is real. It returns the first element of any array and preserves the element type — no any, no casting:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // n: number | undefined
const s = first(["a", "b"]); // s: string | undefinedThe power grows with multiple type parameters and constraints (extends). A constraint says "T can be anything, as long as it has this shape," which lets you touch the value safely while staying generic. Here pluck reads a key from an object, and keyof T guarantees the key exists:
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const age = pluck({ name: "Ada", age: 36 }, "age"); // age: number
pluck({ name: "Ada" }, "email"); // ❌ error: "email" isn't a keyThe rule of thumb: reach for a generic when a function's output type depends on its input type. If the type is always the same (say, always string), you don't need one. When it flows through the function, a type parameter keeps that connection alive — turning a one-off helper into a genuinely reusable tool.