TypeScript generics are one of the most powerful features of the language and one of the most confusing for developers who are new to typed languages. Here is a clear progression from the concept to practical application.
The Problem Generics Solve
Without generics, you face a choice: write the same logic for every type (repetitive), use any (loses type safety), or use unknown (requires casting). Generics let you write code that works with any type while preserving type information through the operation.
// Without generics — loses type information
function firstItem(arr: any[]): any { return arr[0]; }
const item = firstItem([1, 2, 3]); // item: any — TypeScript doesn't know it's a number
// With generics — type information preserved
function firstItem(arr: T[]): T { return arr[0]; }
const item = firstItem([1, 2, 3]); // item: number — TypeScript knows
const str = firstItem(['a', 'b']); // str: string
Type Constraints
Generics can be constrained to require specific properties using extends:
interface HasId { id: number; }
function getById(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
// Now TypeScript knows T has .id — it will error if you pass objects without id
Generic Interfaces and Types
interface ApiResponse {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse;
type PostResponse = ApiResponse;
// UserResponse.data is User; PostResponse.data is Post[]
When Generics Are Worth It
Use generics for: utility functions that work with collections (filter, map, sort), shared data structures (trees, queues, result types), API response wrappers, and React component prop types that vary. Don’t use generics for: functions that only ever deal with one type, components with fixed props, or any case where a simpler approach is clear. Overuse of generics produces code that is technically correct but harder to read than it needs to be.




