Mastering TypeScript Generics
Dinesh SutiharJanuary 30, 202611 min read
TypeScriptJavaScriptProgrammingWeb Development
What Are Generics?
Generics allow you to write reusable code that works with multiple types while maintaining type safety. Instead of using 'any' or duplicating code for each type, generics let you create flexible, type-safe abstractions.
Basic Generic Syntax
typescript
// Generic functionfunction identity<T>(value:T):T { return value;} const num = identity<number>(42); // T is numberconst str = identity("hello"); // T inferred as string // Generic interfaceinterface Box<T> { value:T; getValue():T;} const numBox:Box<number> = { value: 42, getValue() { return this.value; }};Generic Constraints
Constraints limit the types that can be used with a generic. Use 'extends' to specify requirements.
typescript
// Constraint: T must have a length propertyinterface HasLength { length: number;} function logLength<T extends HasLength>(item:T): number { console.log(item.length); return item.length;} logLength("hello"); // ✓ strings have lengthlogLength([1, 2, 3]); // ✓ arrays have lengthlogLength({ length: 10 }); // ✓ object with length// logLength(123); // ✗ numbers don't have length // Constraint with keyoffunction getProperty<T, K extends keyof T>(obj:T, key:K):T[K] { return obj[key];} const person = { name: "Alice", age: 30 };getProperty(person, "name"); // ✓ returns stringgetProperty(person, "age"); // ✓ returns number// getProperty(person, "email"); // ✗ "email" not in personMultiple Type Parameters
typescript
// Tuple creation with two typesfunction pair<T, U>(first:T, second:U): [T, U] { return [first, second];} const p = pair("hello", 42); // [string, number] // Practical example: Map functionfunction mapArray<T, U>(arr:T[], fn: (item:T) => U):U[] { return arr.map(fn);} const nums = [1, 2, 3];const strings = mapArray(nums, n => n.toString()); // string[]Generic Classes
typescript
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 | undefinedBuilt-in Utility Types
TypeScript provides powerful utility types that leverage generics:
typescript
interface User { id: number; name: string; email: string; password: string;} // Partial - all properties optionaltype PartialUser = Partial<User>; // Required - all properties requiredtype RequiredUser = Required<PartialUser>; // Pick - select specific propertiestype UserCredentials = Pick<User, 'email' | 'password'>; // Omit - exclude specific propertiestype PublicUser = Omit<User, 'password'>; // Record - create object type with specific keystype UserRoles = Record<string, 'admin' | 'user' | 'guest'>; // ReturnType - extract return type of functionfunction getUser() { return { id: 1, name: 'Alice' }; }type UserReturn = ReturnType<typeof getUser>;Conditional Types
typescript
// Basic conditional typetype IsString<T> = T extends string ? true : false; type A = IsString<string>; // truetype B = IsString<number>; // false // Practical: Extract array element typetype ElementType<T> = T extends (infer E)[] ? E : never; type NumElement = ElementType<number[]>; // numbertype StrElement = ElementType<string[]>; // string // Exclude and Extracttype T1 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'type T2 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'Real-World Pattern: API Response
typescript
// Generic API response wrapperinterface ApiResponse<T> { data:T; success: boolean; message?: string; timestamp:Date;} interface PaginatedResponse<T> extends ApiResponse<T[]> { pagination: { page: number; limit: number; total: number; };} // Usage with async functionsasync function fetchUser(id: string):Promise<ApiResponse<User>> { const res = await fetch(`/api/users/${id}`); return res.json();} async function fetchUsers():Promise<PaginatedResponse<User>> { const res = await fetch('/api/users'); return res.json();}Conclusion
Generics are essential for writing scalable TypeScript code. Start with simple generic functions, then gradually incorporate constraints and utility types. The key is finding the right balance between flexibility and type safety.