What Are TypeScript Generics?
Generics let you write code that works with multiple types while preserving type information. Without generics, you'd choose between losing type safety (using any) or writing duplicate code for each type. Generics give you a third option: write it once, typed for everything.
// Without generics — loses type info
function first(arr: any[]): any {
return arr[0];
}
// With generics — type-safe and reusable
function first<T>(arr: T[]): T {
return arr[0];
}
const num = first([1, 2, 3]); // num: number
const str = first(['a', 'b']); // str: stringGeneric Functions
The most common use of generics is in utility functions that operate on arrays or data structures:
function groupBy<T, K extends string | number>(
arr: T[],
key: (item: T) => K
): Record<K, T[]> {
return arr.reduce((groups, item) => {
const groupKey = key(item);
return {
...groups,
[groupKey]: [...(groups[groupKey] || []), item],
};
}, {} as Record<K, T[]>);
}
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Carol', role: 'admin' },
];
const byRole = groupBy(users, u => u.role);
// byRole: RecordGeneric Interfaces
Generic interfaces let you describe data structures that work with any type:
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
// Usage
type UserResponse = ApiResponse<User>;
type UsersPage = PaginatedResponse<User>;Generic Constraints
Use extends to restrict what types a generic can accept:
// T must have an 'id' property
function findById<T extends { id: number }>(
items: T[],
id: number
): T | undefined {
return items.find(item => item.id === id);
}
// K must be a key of T
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
const names = pluck(users, 'name'); // string[]
// pluck(users, 'invalid') — TypeScript error!Conditional Types
Conditional types are generics that select a type based on a condition:
type NonNullable<T> = T extends null | undefined ? never : T;
type IsString<T> = T extends string ? true : false;
// Unwrap a Promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;
type StringResult = Awaited<Promise<string>>; // string
type NumberResult = Awaited<number>; // numberThe infer Keyword
The infer keyword extracts a type from a conditional type expression:
// Extract the return type of a function
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// Extract the element type of an array
type ElementType<T> = T extends (infer E)[] ? E : never;
type StringElement = ElementType<string[]>; // stringCommon Generic Patterns
Optional properties with DeepPartial:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};ReadOnly recursive:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};For a complete reference of TypeScript's built-in generic utility types, see our TypeScript Utility Types guide.
