Generics
Writing reusable components that can work over a variety of types.
🧑🏫 Sabse pehle — simple mein samjho#
Socho tum ek factory bana rahe ho jahan alag-alag daanchon (molds) mein plastic daalo to alag khilaune bante hain. Generics wahi daanche (Variables) hote hain. Jaise functions mein hum "values" as variables (arguments) paas karte hain, Generics mein hum "Types" as variables paas karte hain.
Bina Generics ke tumhe har ek type ke liye ek naya function banana padega ya phir any lagana padega jo unsafe hai.
Why Generics?#
Suppose you need to extract a random item from an array.
// The Problem:
function getRandomItem(items: number[]): number { ... } // Only works for number arrays
function getRandomString(items: string[]): string { ... } // Must be rewritten for strings
// The Solution via Generics! (Accepting a type as a variable using <T>)
function getRandomElement<T>(items: T[]): T {
const randomIndex = Math.floor(Math.random() * items.length);
return items[randomIndex];
}
// The single function above now works for arrays of any type:
let num = getRandomElement<number>([1, 2, 3]); // Return type is inferred as number
let str = getRandomElement<string>(["A", "B", "C"]); // Return type is inferred as string
// Inference (Even simpler): TS can automatically guess the value of T based on arguments
let b = getRandomElement([true, false]); // Here T is automatically boolean
Generic Interfaces / Types#
You can pass generics into type and interface definitions as well (just like passing parameters to a function). This is highly utilized when defining API response structures.
interface ApiResponse<DataShape> {
status: number;
isError: boolean;
data: DataShape; // This will dynamically adapt!
}
// Data shape for a User API
type User = { name: string; age: number; };
const res1: ApiResponse<User> = { status: 200, isError: false, data: { name: "Tarun", age: 25 } };
// Data shape for a Products API (Reusing the exact same interface)
const res2: ApiResponse<string[]> = { status: 200, isError: false, data: ["Laptop", "Phone"] };
Generic Constraints (extends)#
Sometimes you need to limit (restrict) what a Generic can accept. "You can accept any type T, but it MUST have a length property (like an array or string)." These are called Constraints.
// The Rule: T must be an object that possesses a 'length' property
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): void {
console.log(item.length); // No error here because TS guarantees 'length' exists
}
logLength("Hello"); // ✅ Valid (strings have length)
logLength([1, 2, 3]); // ✅ Valid (arrays have length)
// logLength(123); // ❌ ERROR: numbers do not have a length property
Multiple Generics#
You can pass more than one generic type parameter, like so:
function mergeObjects<T, U>(obj1: T, obj2: U) {
return { ...obj1, ...obj2 };
}
React's useState<string>("") is one of the best everyday examples of a perfectly generic function!