What you'll learn
Quick Answer
TypeScript generics let you write functions, interfaces and classes that work with many types while still staying type-safe. Instead of hard-coding a type like string, you use a type variable (usually written T) that gets filled in when the code is used. This gives you reusable code with full autocomplete and error checking, which is why generics are almost always a better choice than any.
What are TypeScript generics?
TypeScript generics are a way to write code that works with many different types without giving up type safety. Think of a generic as a type variable: a placeholder for a type that gets decided later, when someone actually uses your code.
Here is the everyday problem generics solve. Say you write a function that returns the first item of an array. If you write it for numbers, it only works for numbers. If you copy-paste it for strings, you now maintain two copies. Generics let you write it once and use it with numbers, strings, users, or anything else, while TypeScript still knows exactly what type comes back.
By convention the type variable is written as a single capital letter like T (short for "Type"), but it is just a name. You will also see K for key, V for value, and E for element. Let us build up from the simplest possible example.
Start with a generic identity function
The classic first example is an identity function: it takes a value and returns it unchanged. It sounds useless, but it is the clearest way to see how a type variable flows through a function.
function identity<T>(value: T): T {
return value;
}
const a = identity<string>("hello"); // a is a string
const b = identity<number>(42); // b is a numberRead <T> as "this function has a type variable called T". The parameter is typed value: T and the return type is also T, so whatever type you pass in is the exact type you get back. Pass a string, get a string; pass a number, get a number.
You usually do not even need to write the type by hand. TypeScript can infer it from the argument:
const c = identity("world"); // T is inferred as string
c.toUpperCase(); // works, autocomplete knows it is a stringThat inference is the magic. You get full autocomplete and compile-time checks for free, without repeating type names everywhere.
Why generics beat any
Beginners often reach for any to make a "works with everything" function. It compiles, but it quietly switches off all type checking, which defeats the point of using TypeScript.
function identityAny(value: any): any {
return value;
}
const name = identityAny("Priya");
name.toFixed(2); // NO error at compile time, but crashes at runtimeHere toFixed is a number method, but we called it on a string. Because the return type is any, TypeScript stays silent and the bug reaches production. With the generic version, the same mistake is caught immediately:
const safeName = identity("Priya"); // T is string
safeName.toFixed(2); // Error: Property 'toFixed' does not exist on type 'string'Generics keep the flexibility of accepting any type while remembering which type it actually was. That is the whole difference.
| Feature | any | Generics |
| Works with any type | Yes | Yes |
| Keeps type information | No | Yes |
| Autocomplete on the result | No | Yes |
| Catches type mistakes | No | Yes |
Generic interfaces and type aliases
Generics are not just for functions. Interfaces and type aliases can take type variables too, which is how you describe flexible data shapes. A very common pattern is an API response wrapper:
interface ApiResponse<T> {
status: number;
data: T;
}
interface User {
id: number;
name: string;
}
const res: ApiResponse<User> = {
status: 200,
data: { id: 1, name: "Aarav" },
};
console.log(res.data.name); // fully typed, autocomplete worksThe same ApiResponse shape now works for a user, a list of products, or anything else, just by changing what you put inside the angle brackets: ApiResponse<Product[]>.
Type aliases can take more than one type variable. Here is a simple key-value pair:
type Pair<K, V> = {
key: K;
value: V;
};
const score: Pair<string, number> = { key: "math", value: 95 };Two type variables, two independent types. TypeScript checks that key is a string and value is a number.
Constraints with extends
Sometimes you do not want to accept literally every type, only types that have a certain feature. That is what extends does inside a generic: it sets a constraint.
Say you want a function that logs the .length of whatever you pass. Plain T would not work, because not every type has a length. Constrain it:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // strings have length
logLength([1, 2, 3]); // arrays have length
logLength(42); // Error: number has no 'length' propertyThe line T extends HasLength means "T can be any type, as long as it has a numeric length property". You keep flexibility but rule out the types that would break.
A powerful cousin uses keyof to safely read a property by name:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const student = { name: "Ravi", marks: 88 };
const marks = getProp(student, "marks"); // type is number
const who = getProp(student, "name"); // type is string
getProp(student, "age"); // Error: 'age' is not a keyHere K extends keyof T forces the key to be a real property name of the object, and T[K] returns exactly that property's type. Typos become compile errors instead of runtime undefined.
Generic container types (React-style)
The place generics really shine is reusable container components and structures that hold "some type of thing". If you have worked with React, this pattern will feel familiar. Imagine a list renderer whose props are generic over the item type:
interface ListProps<T> {
items: T[];
renderItem: (item: T) => string;
}
function List<T>(props: ListProps<T>): string {
return props.items.map(props.renderItem).join(", ");
}
const numbers = List({
items: [1, 2, 3],
renderItem: (n) => `#${n}`,
});
const names = List({
items: ["Ravi", "Meera"],
renderItem: (person) => person.toUpperCase(),
});Notice that inside renderItem, TypeScript already knows n is a number and person is a string. You did not annotate them; the generic linked items and renderItem together so the item type flows into the callback automatically.
A simple "box" that holds one value follows the same idea:
interface Box<T> {
value: T;
label: string;
}
function wrap<T>(value: T, label: string): Box<T> {
return { value, label };
}
const intBox = wrap(10, "count"); // Box<number>
const textBox = wrap("hi", "message"); // Box<string>This is exactly how real component libraries type things like dropdowns, tables and form fields. If you are heading toward front-end work, our React course uses this pattern constantly. To build the TypeScript foundation first, start with the free TypeScript course.
Common gotchas to remember
Generics are friendly once a few surprises are out of the way:
- Types disappear at runtime. TypeScript erases generics when it compiles to JavaScript. You cannot do
new T()orvalue instanceof T, becauseTdoes not exist when the code runs. Pass a factory function or a real class if you need runtime behaviour. - Do not over-genericize. If a function only ever handles strings, just use
string. Reach for a generic when you have a real "same code, different types" situation. A generic used in only one spot usually adds noise, not value. - Let inference do the work. Writing
identity<string>("hi")is fine, but usuallyidentity("hi")is enough. Add explicit type arguments only when TypeScript cannot figure it out or gets it wrong. - Default type parameters exist. You can give a fallback with
<T = string>so callers can skip it. Handy for config objects and library APIs. - A single letter is not required. For a public API, a descriptive name like
<TItem>or<TResponse>reads better than a loneT.
Recommendation and next steps
Here is the practical rule of thumb. Whenever you feel tempted to write any just to make something "work with everything", stop and ask whether a generic would keep the type information instead. Nine times out of ten it will, and you get autocomplete and error checking as a bonus.
A good learning path:
- Get comfortable with the generic identity function until
<T>feels natural. - Add a generic interface like
ApiResponse<T>to real code you already have. - Introduce a constraint with
extendsthe first time you need a specific property. - Build one small generic container, such as a typed list or box, to see inference flow through callbacks.
Do not try to memorise every advanced feature at once. Generics click through repetition, not theory. Write a few of your own, let the compiler correct you, and the pattern quickly becomes second nature.
Rule of thumb: if you were about to reach for any, try a generic first. You almost always keep more safety for the same effort.Ready to practise with guided examples and exercises? The free Priodemy TypeScript course walks through generics step by step, from basics to real project code.
Frequently Asked Questions
What does the letter T mean in TypeScript generics?
T is just a conventional name for a type variable, short for "Type". It is a placeholder that gets replaced by a real type (like string or number) when the code is used. You can name it anything you like, such as TItem or TResponse; the compiler does not care about the name, only that it is a type variable declared inside angle brackets.
When should I use generics instead of any?
Use a generic whenever you want code to work with many types but still keep type safety. any turns off type checking completely, so mistakes slip through to runtime. Generics remember the actual type, so you keep autocomplete and compile-time error checking. A good habit is: any time you are about to write any, try a generic first.
What does extends do in a generic?
Inside a generic, extends sets a constraint on the type variable. For example, <T extends { length: number }> means T can be any type as long as it has a numeric length property. This lets you safely use that property inside the function while still rejecting types that do not have it, such as a plain number.
Do TypeScript generics affect the compiled JavaScript?
No. Generics exist only during type checking and are completely erased when TypeScript compiles to JavaScript. This means there is no runtime cost, but it also means you cannot do things like new T() or value instanceof T, because the type variable no longer exists at runtime. If you need runtime behaviour, pass a class or factory function explicitly.
Do I always have to write the type in angle brackets when calling a generic function?
Usually not. TypeScript can infer the type from the arguments you pass, so identity("hi") works just as well as identity<string>("hi"). Write the explicit type argument only when inference cannot determine it, or when you want to force a specific type that differs from what would be inferred.
Can an interface or class be generic, or only functions?
All of them can be generic. Functions, interfaces, type aliases and classes can each declare type variables. Generic interfaces like ApiResponse<T> are extremely common for describing flexible data shapes, and generic classes are the basis of reusable containers such as typed lists, stacks and queues.
