Generics let a type be supplied later, at the point where a function, type, interface, or class is used. Instead of writing separate logic for strings, numbers, users, products, and every other value shape, you write one reusable structure that preserves the exact type relationship.
The most important idea is this: generics do not mean "anything with no checking". They mean "this code works with many types, and TypeScript should remember which exact type is being used right now." That is why generics are safer than using any.
function identity<T>(value: T): T {
return value;
}
const name = identity<string>("Asha");
const score = identity<number>(95);
const active = identity(true); // TypeScript infers boolean
console.log(name.toUpperCase());
console.log(score.toFixed(1));
console.log(active ? "yes" : "no");
any removes type checking. A function that accepts and returns any may look flexible, but callers lose information about the value they passed in. Generics keep flexibility without throwing away safety.
A generic function says, "give me a value of some type, and I will return that same type." An any function says, "give me something, and I will return something unchecked." That difference matters as a codebase grows.
function firstAny(items: any[]): any {
return items[0];
}
function first<T>(items: T[]): T | undefined {
return items[0];
}
const unsafeUser = firstAny([{ name: "Admin" }]);
unsafeUser.notARealMethod(); // No compiler warning because it is any
const safeUser = first([{ name: "Admin" }]);
// safeUser.notARealMethod(); // Error
console.log(safeUser?.name);
A constraint limits what a generic type can be. This is useful when the function needs to access a property or method. Without a constraint, TypeScript only knows that T could be anything, so it will not allow property access.
Use extends to say that a generic type must have a minimum shape. The caller can still pass extra properties, but the required properties must exist.
| Pattern | Meaning |
|---|---|
<T> | Any type can be supplied. |
<T extends string> | Only string-like values are allowed. |
<T extends { id: number }> | The value must have an id property. |
function getLabel<T extends { label: string }>(item: T): string {
return item.label;
}
const course = getLabel({ id: 1, label: "TypeScript" });
const product = getLabel({ sku: "BK-10", label: "Book", price: 299 });
console.log(course);
console.log(product);
// getLabel({ id: 2 });
// Error: label is required by the constraint.
Generics are not only for functions. You can use them in type aliases and interfaces to describe reusable containers. This is common for API responses, pagination, form state, table rows, dropdown options, and cache entries.
The container shape stays the same, while the inner data type changes per use case.
type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
type User = { id: number; name: string };
type BlogPost = { id: number; title: string; slug: string };
const userResponse: ApiResponse<User> = {
data: { id: 1, name: "Admin" },
status: 200,
message: "OK",
};
const postResponse: ApiResponse<BlogPost[]> = {
data: [{ id: 10, title: "Generics", slug: "generics" }],
status: 200,
message: "OK",
};
A class can also be generic. This is useful when a class stores, manages, or transforms values but should not care about the exact item type. Examples include repositories, queues, collections, caches, and event emitters.
When you create an instance, the generic type tells TypeScript what values the instance can accept and return.
type Entity = { id: number };
class Repository<T extends Entity> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
findById(id: number): T | undefined {
return this.items.find(item => item.id === id);
}
}
const users = new Repository<{ id: number; name: string }>();
users.add({ id: 1, name: "Asha" });
console.log(users.findById(1)?.name);
Use generics when there is a real relationship between types. A function that accepts a value and returns the same value is a good fit. A response wrapper that contains different data per endpoint is a good fit. A dropdown component that receives options and returns the selected option is also a good fit.
Avoid adding generics just to look advanced. If a function only ever works with one concrete type, a normal type annotation is clearer.
any because callers keep their specific types.
extends let generic code safely use required properties.
Explore 500+ free tutorials across 20+ languages and frameworks.