BeCuriouzBeCuriouz

Learn something new every day

🔷 TypeScript
🛠️

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:

ts
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 | undefined

The 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:

ts
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 key

The 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.

🛠️ Practical Generics: Utilities That Work With Any Type — BeCuriouz