TypeScript generics allow you to write flexible, reusable components and functions that maintain strong type safety across different data structures. Generics act as type placeholders, enabling developers to write code that adapts to different data models without resorting to unsafe any assertions.
1. Declaring Generic Variables & Interfaces
To create a generic container, declare a type parameter inside angle brackets (usually <T>). This enforces type consistency across inputs and outputs:
// Generic API response container interface
interface ApiResponse<T> {
data: T;
status: 'success' | 'error';
timestamp: string;
}
async function fetchJson<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
const parsed = await res.json();
return {
data: parsed as T,
status: 'success',
timestamp: new Date().toISOString()
};
}
2. Using Generic Constraints
Sometimes, you want to limit the types a generic can accept. For example, if a function reads a .length property, you must constrain the generic type to objects that implement that property:
interface HasLength {
length: number;
}
function printLength<T extends HasLength>(item: T): void {
console.log("Length is:", item.length);
}
printLength("Hello"); // Valid (strings have length)
printLength([1, 2, 3]); // Valid (arrays have length)
// printLength(123); // Error (numbers do not have length)
Using constraints ensures that generic functions are reusable while remaining type-safe. You can also combine parameters using key constraints (keyof) to query properties on objects safely.