TypeScript Generics
Generics allow writing code that works with multiple types while still maintaining full type safety. Instead of writing the same function or class multiple times for each type, a generic version handles all types with a single definition. The type is specified when the code is used, not when it is written. This makes code reusable, flexible, and type-safe at the same time.
The Problem Generics Solve
Without generics — repeat the same function for each type:
function getFirstNumber(arr: number[]): number { return arr[0]; }
function getFirstString(arr: string[]): string { return arr[0]; }
function getFirstBoolean(arr: boolean[]): boolean { return arr[0]; }
With generics — one function handles all types:
function getFirst<T>(arr: T[]): T { return arr[0]; }
getFirst([1, 2, 3]); // T = number → returns number
getFirst(["a", "b", "c"]); // T = string → returns string
getFirst([true, false, true]); // T = boolean → returns boolean
Generic Syntax
function identity<T>(value: T): T {
| | |
| | +-- Return type uses T
| +------ Parameter uses T
+---------------- T is the type parameter (placeholder)
}
The letter T is just a convention (short for "Type"). Any identifier works — U, K, V, or descriptive names like ItemType.
Generic Functions
function identity<T>(value: T): T {
return value;
}
console.log(identity<string>("Hello")); // Hello
console.log(identity<number>(42)); // 42
console.log(identity<boolean>(true)); // true
// TypeScript can also infer T from the argument
console.log(identity("TypeScript")); // T inferred as string
console.log(identity(100)); // T inferred as number
Generic with Arrays
function reverseArray<T>(items: T[]): T[] {
return items.slice().reverse();
}
let numbers = reverseArray([1, 2, 3, 4, 5]);
console.log(numbers); // [5, 4, 3, 2, 1]
let names = reverseArray(["Arun", "Bina", "Chetan"]);
console.log(names); // ["Chetan", "Bina", "Arun"]
Multiple Type Parameters
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
let item1 = pair("name", "Roshni"); // [string, string]
let item2 = pair(1, "TypeScript"); // [number, string]
let item3 = pair("age", 25); // [string, number]
console.log(item2); // [1, "TypeScript"]
Generic Interfaces
interface ApiResponse<T> {
status: number;
message: string;
data: T;
}
// Reuse with different data types
let userResponse: ApiResponse<{ name: string; email: string }> = {
status: 200,
message: "Success",
data: { name: "Shilpa", email: "shilpa@mail.com" }
};
let coursesResponse: ApiResponse<string[]> = {
status: 200,
message: "Courses loaded",
data: ["TypeScript", "React", "Node.js"]
};
console.log(userResponse.data.name); // Shilpa
console.log(coursesResponse.data.length); // 3
Generic Classes
class DataStore<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getAll(): T[] {
return this.items;
}
getById(index: number): T {
return this.items[index];
}
count(): number {
return this.items.length;
}
}
// String store
let nameStore = new DataStore<string>();
nameStore.add("Gaurav");
nameStore.add("Hema");
console.log(nameStore.getAll()); // ["Gaurav", "Hema"]
// Number store
let scoreStore = new DataStore<number>();
scoreStore.add(88);
scoreStore.add(95);
scoreStore.add(72);
console.log(scoreStore.count()); // 3
Generic Constraints
A generic constraint limits what types can be used. The extends keyword applies the constraint — the type parameter must extend (be compatible with) the specified type.
// T must have a 'length' property — strings and arrays qualify, numbers do not
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
console.log(getLength("Hello")); // 5 — string has length
console.log(getLength([1, 2, 3, 4])); // 4 — array has length
console.log(getLength(42)); // Error: number has no 'length' property
keyof Constraint
The keyof operator produces a union of all property names of a type. Using it as a constraint ensures a parameter is a valid key of another type parameter.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let student = {
name: "Isha",
age: 19,
grade: "A"
};
console.log(getProperty(student, "name")); // Isha
console.log(getProperty(student, "age")); // 19
console.log(getProperty(student, "score")); // Error: "score" is not a key of student
Default Type Parameters
interface Container<T = string> {
value: T;
label: string;
}
// Uses default type string
let box1: Container = { value: "TypeScript", label: "Language" };
// Explicitly specifies number
let box2: Container<number> = { value: 42, label: "Answer" };
console.log(box1.value.toUpperCase()); // TYPESCRIPT
console.log(box2.value.toFixed(2)); // 42.00
Generic Type Summary
| Generic Use | Syntax | Purpose |
|---|---|---|
| Function | function f<T>(x: T): T | Type-safe reusable function |
| Interface | interface I<T> { data: T } | Reusable object shape |
| Class | class C<T> { items: T[] } | Reusable data container |
| Constraint | T extends SomeType | Limit what types T can be |
| keyof constraint | K extends keyof T | Ensure K is a valid key of T |
| Default type | T = DefaultType | Fallback type when T is not specified |
Practical Example
// Generic pagination helper
interface PaginatedResult<T> {
items: T[];
currentPage: number;
totalPages: number;
totalItems: number;
hasNextPage: boolean;
}
function paginate<T>(allItems: T[], page: number, perPage: number): PaginatedResult<T> {
let start = (page - 1) * perPage;
let end = start + perPage;
let items = allItems.slice(start, end);
let totalPages = Math.ceil(allItems.length / perPage);
return {
items,
currentPage: page,
totalPages,
totalItems: allItems.length,
hasNextPage: page < totalPages
};
}
let courses = ["TypeScript", "React", "Node.js", "MongoDB", "Express", "GraphQL", "Docker"];
let page1 = paginate(courses, 1, 3);
console.log("Page 1:", page1.items); // ["TypeScript", "React", "Node.js"]
console.log("Total Pages:", page1.totalPages); // 3
console.log("Has Next:", page1.hasNextPage); // true
let page3 = paginate(courses, 3, 3);
console.log("Page 3:", page3.items); // ["Docker"]
console.log("Has Next:", page3.hasNextPage); // false
