TypeScript Generics Explained Simply
TypeScript Generics Explained Simply
Generics let you write functions, classes, and types that work with any data type while preserving type safety. Think of them as type parameters — placeholders that get filled in when you use the code.
The Problem Generics Solve
Without generics, you either lose type information or duplicate code:
// Option 1: Lose type info with 'any'
function firstElement(arr: any[]): any {
return arr[0]; // Caller gets 'any' back — no help from TypeScript
}
// Option 2: Duplicate for every type
function firstString(arr: string[]): string { return arr[0]; }
function firstNumber(arr: number[]): number { return arr[0]; }
Your First Generic Function
Add a type parameter in angle brackets. By convention, T stands for "Type":
function firstElement(arr: T[]): T | undefined {
return arr[0];
}
const num = firstElement([1, 2, 3]); // type: number
const str = firstElement(["a", "b", "c"]); // type: string
TypeScript infers T from the argument. You rarely need to specify it explicitly.
Constraining Generics
Use extends to limit what types are accepted:
function getLength(item: T): number {
return item.length;
}
getLength("hello"); // OK — strings have .length
getLength([1, 2, 3]); // OK — arrays have .length
getLength(42); // Error — numbers don't have .length
Generic Interfaces and Types
Generics work with interfaces and type aliases:
interface ApiResponse {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse<{ id: string; name: string }>;
type PostResponse = ApiResponse<{ id: string; title: string; body: string }>;
Multiple Type Parameters
Use multiple parameters when you need to track several types:
function mapObject(
obj: Record,
fn: (value: V) => R
): Record {
const result = {} as Record;
for (const key in obj) {
result[key] = fn(obj[key]);
}
return result;
}
const prices = { apple: 1.5, banana: 0.75 };
const formatted = mapObject(prices, (v) => $\${v.toFixed(2)});
// type: Record<"apple" | "banana", string>
Practical Patterns
Generic React Components
interface ListProps {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List({ items, renderItem }: ListProps) {
return
{items.map((item, i) => - {renderItem(item)}
)}
;
}
// Usage — TypeScript infers T from items
{user.name}} />
Utility Types Are Generics
Built-in utilities like Partial, Pick, and Record are all generic types.
Common Mistakes
T only appears once in the signature, you probably don't need it.extends to tell TypeScript what properties T has.Conclusion
Generics are about writing reusable code without sacrificing type safety. Start with simple functions, graduate to interfaces and constraints, and use them when you find yourself duplicating logic for different types.