Demystifying TypeScript Generics
Generics are scary at first, but they are the key to unlocking true reusability without sacrificing type safety.They allow you to write functions that work with any data type while still preserving the relationship between input and output.
The Problem with "any"
Imagine you want to write a function that returns the first element of an array.
Without generics, you have two bad choices:
// Choice 1: Specificity (Not reusable)
function getFirst(arr: number[]): number {
return arr[0];
}
// Choice 2: "any" (Not safe)
function getFirst(arr: any[]): any {
return arr[0];
}
The first one doesn't work for strings. The second one destroys strictness. If you use any, you lose autocomplete and safety.
Enter the Variable for Types
Generics are just function arguments for types.
function getFirst& lt;lt; T & gt;gt; (arr: T[]): T
This reads as: "This function takes a type variable T. It accepts an array of Ts. It returns a T."
Now if you pass [1, 2, 3] , TypeScript infers T is number.If you pass ["a", "b"] , T is string.You get safety AND reusability.
Real World Example: API Response Wrapper
In a React app, your backend usually wraps data in a standard response envelope.
interface ApiResponse & lt;lt; Data & gt;gt; {
data: Data;
status: number;
error ?: string;
}
interface User {
id: string;
name: string;
}
// Usage
const response: ApiResponse & lt;lt; User & gt;gt; = await fetchUser();
console.log(response.data.name); // Typescript knows "name" exists!
console.log(response.data.age); // Error: Property 'age' does not exist
This is where generics shine.You define the structure once, and fill in the content later.
Constraints: "T extends..."
Sometimes you don't want T to be anything. You want it to be something with a specific capability.
function logLength& lt;lt;T extends { length: number }& gt;gt; (arg: T) {
console.log(arg.length);
}
logLength("hello"); // OK (string has .length)
logLength([1, 2]); // OK (array has .length)
logLength(100); // Error (number does not have .length)
Theextends keyword acts as a filter.
Advanced: "keyof" and Lookup Types
Generics get powerful when combined with keyof . This allows you to create safe object accessors.
function getProperty& lt;lt; T, K extends keyof T & gt;gt; (obj: T, key: K) {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // OK
getProperty(user, "email"); // Error: Argument "email" is not assignable to "name" | "age"
This pattern prevents a huge class of bugs where you mistype property names.
Conclusion
Generics are the difference between "Code that works" and "Code that scales." They compel you to think about the shape of your data abstractly. Once you master <T>, you never go back.