Generic Functions & Constraints
A type parameter is inferred, not usually written
Section titled “A type parameter is inferred, not usually written”Most of the time you never write the type argument — inference fills it in from the value you pass:
function first<T>(arr: T[]): T | undefined { return arr[0];}
const a = first([1, 2, 3]);// ^? number | undefined — T inferred as numberconst b = first(["x", "y"]);// ^? string | undefined — T inferred as stringYou can pass it explicitly (first<number>([1, 2, 3])), but you rarely need to. The type parameter flows from the call site.
Constraints: extends limits what T can be
Section titled “Constraints: extends limits what T can be”An unconstrained T can be anything, so you can’t assume it has any properties. When your function needs T to have some shape, constrain it with extends:
// ❌ without a constraint, T might not have .lengthfunction longest<T>(a: T, b: T) { return a.length > b.length ? a : b; // Error: Property 'length' does not exist on type 'T'}
// ✅ constrain T to things that have a numeric lengthfunction longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b;}
longest([1, 2], [1, 2, 3]); // ✅ arrays have length → returns number[]longest("ab", "abc"); // ✅ strings have length → returns stringlongest(1, 2); // ❌ number has no lengthRead T extends { length: number } as “T must be assignable to { length: number }” — i.e. T is some subtype that has at least a numeric length. Crucially, the return type is still T, so you keep the specific type (number[], string) rather than widening to the constraint.
keyof constraints: a safe property getter
Section titled “keyof constraints: a safe property getter”A constraint can reference another type parameter. The classic example is a type-safe get:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}
const user = { name: "Ada", age: 36 };const name = getProp(user, "name");// ^? stringconst age = getProp(user, "age");// ^? numbergetProp(user, "email"); // ❌ "email" is not a key of userK extends keyof T says the key must be one of T’s actual keys, and the return type T[K] is the type of that specific property. This is the backbone of typed object utilities.
Default type parameters
Section titled “Default type parameters”Like default function arguments, a type parameter can have a default that’s used when it can’t be inferred and isn’t supplied:
interface Container<T = string> { value: T;}
const c1: Container = { value: "hi" }; // T defaults to stringconst c2: Container<number> = { value: 1 }; // T explicitly numberDefaults are common on generic classes and interfaces where a sensible fallback exists.
Don’t over-generalize
Section titled “Don’t over-generalize”Generics are a tool, not a goal. A type parameter that appears only once in a signature is usually a mistake — it’s not linking anything, so it might as well be a concrete type or unknown:
// pointless generic: T is used once, links nothingfunction log<T>(x: T): void { console.log(x); }// just write:function log(x: unknown): void { console.log(x); }A generic earns its keep when the type parameter connects two or more positions (input to output, or one parameter to another). If it doesn’t create a relationship, drop it.