Generic Functions
Generic Functions
Generics allow you to write reusable code that works with any type while preserving type safety.
Basic Generic Function
// Without generics - lose type information
function identity(value: any): any {
return value;
}
const result = identity('hello'); // type: any (bad)
// With generics - preserve type information
function identity<T>(value: T): T {
return value;
}
const result2 = identity('hello'); // type: string (good)
const result3 = identity(42); // type: number
Multiple Type Parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p1 = pair('hello', 42); // [string, number]
const p2 = pair(true, [1, 2, 3]); // boolean, number[]
// Generic function with callback
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
return array.map(fn);
}
const doubled = map([1, 2, 3], (n) => n * 2); // number[]
const strings = map([1, 2, 3], (n) => String(n)); // string[]
Generic Constraints
Generic Constraints
Constrain type parameters to have certain properties using the extends keyword.
Basic Constraints
// T must have a 'length' property
function logLength<T extends { length: number }>(item: T): void {
console.log(`Length: ${item.length}`);
}
logLength('hello'); // OK: string has length
logLength([1, 2, 3]); // OK: array has length
logLength({ length: 5, name: 'test' }); // OK
// logLength(42); // Error: number has no length
keyof Constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: 'Alice', age: 30, email: 'alice@test.com' };
getProperty(person, 'name'); // type: string
getProperty(person, 'age'); // type: number
// getProperty(person, 'phone'); // Error: 'phone' not in keyof person
Class Constraints
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const strContainer = new Container('hello');
const numContainer = new Container(42);
// Constraint: T must be array-like
function first<T extends { [index: number]: unknown; length: number }>(arr: T): T[0] {
return arr[0];
}
first([1, 2, 3]); // number
first(['a', 'b']); // string
Generic Interfaces and Classes
Generic Interfaces and Classes
Generic Interfaces
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
// Typed API response
const userResponse: ApiResponse<User> = {
data: { id: 1, name: 'Alice', email: 'alice@test.com' },
status: 200,
message: 'Success',
timestamp: new Date()
};
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: Omit<T, 'id'>): Promise<T>;
update(id: string, data: Partial<T>): Promise<T | null>;
delete(id: string): Promise<boolean>;
}
Generic Classes
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined
const stringStack = new Stack<string>();
stringStack.push('hello');
Generic Defaults and Advanced Patterns
Generic Defaults and Advanced Patterns
Generic Defaults
// Default type parameter
interface PaginatedResponse<T = unknown> {
data: T[];
page: number;
pageSize: number;
total: number;
}
// Uses default type
const response: PaginatedResponse = {
data: [1, 2, 3],
page: 1,
pageSize: 10,
total: 100
};
// Overrides default type
const userResponse: PaginatedResponse<User> = {
data: [{ id: 1, name: 'Alice', email: 'alice@test.com' }],
page: 1,
pageSize: 10,
total: 50
};
Infer Keyword in Generics
// Extract return type of a function
type ReturnOf<F> = F extends (...args: any[]) => infer R ? R : never;
function getString(): string { return 'hello'; }
type Result = ReturnOf<typeof getString>; // string
// Extract array element type
type ElementOf<T> = T extends (infer E)[] ? E : never;
type NumberArray = number[];
type Num = ElementOf<NumberArray>; // number
Recursive Generics
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
}
type ReadonlyConfig = DeepReadonly<Config>;
// All nested properties are readonly