TypeScript generics
Generics let you write reusable components and functions that work across many types while preserving relationships (for example, “input and output share the same shape”).
Generic functions
Type parameters appear in angle brackets before the parameter list.
function identity<T>(value: T): T {
return value;
}
const n = identity(42); // T inferred as number
const s = identity<string>('x'); // explicit T
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
Multiple type parameters
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
Generic interfaces and type aliases
interface ApiResponse<T> {
data: T;
meta: { requestId: string };
}
type Box<T> = { value: T };
class Stack<T> {
private items: T[] = [];
push(x: T) { this.items.push(x); }
pop(): T | undefined { return this.items.pop(); }
}
Constraints with extends
Require that a type parameter has certain properties or extends a base type.
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
function logLength<T extends { length: number }>(x: T) {
console.log(x.length);
}
logLength('hi');
logLength([1, 2, 3]);
keyof T produces a union of known keys; combined with generics it models safe property access.
Default type parameters
interface FetchOptions<T = unknown> {
url: string;
parse?: (body: string) => T;
}
type Nullable<T = string> = T | null;
If inference does not apply, the default is used; callers can still supply a specific type.
Practical patterns
Wrapped API result
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
async function fetchJson<T>(url: string): Promise<Result<T>> {
try {
const res = await fetch(url);
if (!res.ok) return { ok: false, error: res.statusText };
const raw: unknown = await res.json();
// `as T` does not validate — only use after you trust the origin or parse/validate `raw`
const value = raw as T;
return { ok: true, value };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : 'Unknown' };
}
}
Security: JSON from the network is untrusted. Assertions like as T are erased at compile time and do not protect against malicious or malformed payloads. For production, validate raw with a schema library or explicit checks before treating it as T.
Preserve tuple types
function tuple<T extends readonly unknown[]>(...args: T): T {
return args;
}
const t = tuple(1, 'a', true); // readonly [number, string, boolean]