Generics in TypeScript
Generics let you write reusable, type-safe code by parameterizing types.
Generic functions
function first<T>(xs: T[]): T | undefined {
return xs[0];
}
function mapValues<T, U>(obj: Record<string, T>, f: (x: T) => U): Record<string, U> {
const out: Record<string, U> = {};
for (const k in obj) out[k] = f(obj[k]);
return out;
}
Type arguments are usually inferred at call sites.
Constraints with extends
function pluck<T, K extends keyof T>(list: T[], key: K): T[K][] {
return list.map(item => item[key]);
}
K extends keyof T ties the key to the object shape.
Default type parameters
interface Box<T = string> { value: T }
const b: Box = { value: "hi" }; // T defaulted to string
Generic interfaces and classes
interface Result<T, E = Error> {
ok: boolean;
value?: T;
error?: E;
}
class Queue<T> {
#items: T[] = [];
enqueue(x: T) { this.#items.push(x); }
dequeue(): T | undefined { return this.#items.shift(); }
}
Variance and readonly
Use readonly arrays to avoid accidental unsound writes when variance matters.
function readAll(xs: ReadonlyArray<string>) { xs.forEach(console.log); }
Builder-style APIs with generics
type WithAuth<T> = T & { auth: { userId: string } };
function withAuth<T>(x: T): WithAuth<T> { return Object.assign(x, { auth: { userId: "u1" } }); }
Utility patterns
Array<T>vsT[]: stylistic- Use
Record<K, V>for dictionary types - Prefer generic constraints over
any
Summary
- Let inference do the work; constrain with
extendswhen needed - Defaults reduce verbosity; model APIs with generic interfaces/classes