Generics
Learn how to write flexible, reusable functions and containers that stay fully type-safe by using type parameters — the angle-bracket superpower of TypeScript.
You have probably written a function that works perfectly for one type of data and then thought, "I wish I could reuse this exact logic with a different type." The tempting fix is to reach for any — it works at runtime, but quietly throws away everything TypeScript promises you. Generics are the better answer. They let you write code once and reuse it with any type, while TypeScript still tracks exactly what type flows through at every step. In this lesson you will learn how type parameters work, why they beat any, how to use them on functions and interfaces, and how to add constraints so TypeScript can give you even more help.
Generics are like a labelling machine
Imagine a labelling machine at a warehouse. You feed it a box, it prints a label that says exactly what is inside, and that label travels with the box forever. any is a box marked "stuff" — nobody knows what's in it. A generic is the labelling machine: whatever type you put in, the correct label comes out the other side.
#The Problem: When any Is a Trap
function identity(value: any): any {
return value;
}
const result = identity(42);
// result is typed as `any` — TypeScript has forgotten it is a number
// This would not catch a bug if value were a string:
console.log(result.toFixed(2));#Type Parameters: The <T> Syntax
function identity<T>(value: T): T {
return value;
}
const num = identity(42); // TypeScript infers T = number
const str = identity("hello"); // TypeScript infers T = string
console.log(num.toFixed(2)); // OK: num is number
console.log(str.toUpperCase()); // OK: str is string
// str.toFixed(2); // Error: string has no toFixed#Generic Interfaces and Array Helpers
// Generic interface
interface Box<T> {
label: string;
value: T;
}
const scoreBox: Box<number> = { label: "score", value: 99 };
// Generic array helper — returns the first element or undefined
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const top = first([98, 75, 60]); // type: number | undefined
console.log(top); // 98
console.log(first([])); // undefined#Constraints with extends
Sometimes you do not want any type — you need one that has at least certain properties. The extends keyword adds a constraint to T. TypeScript then lets you safely access those properties inside the function, while still accepting any compatible type.
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(`Length: ${item.length}`);
return item;
}
logLength("typescript"); // Length: 10
logLength([1, 2, 3]); // Length: 3
// logLength(42); // Error: number has no .lengthextends in a constraint is not inheritance
In a class, extends means "inherits from". In a generic constraint, it means "must satisfy this shape". T extends HasLength means: T must have at least a length: number property — not that T must be a subclass of anything.
What does TypeScript infer as the type of `result` in this call? ```ts function identity<T>(value: T): T { return value; } const result = identity(true); ```
Start without constraints, add them when TypeScript asks
Write your generic with just <T> first. If TypeScript reports "Property X does not exist on type T", that is the signal to add an extends constraint. This keeps your generics as flexible as possible and only narrows them when the code actually needs it.
Key takeaways
- A type parameter `<T>` is a placeholder that TypeScript replaces with the real type at each call site — giving you reuse without losing safety.
- Generics preserve type information through functions and containers; `any` throws it away.
- TypeScript usually infers `T` from your arguments, so you rarely need to write the type argument explicitly.
- Use `extends` to constrain `T` to types that have certain properties, unlocking access to those properties inside your function.
- Generic interfaces like `Box<T>` let you build reusable data structures that remain fully typed for any content type.
TypeScript infers T from the argument. What does this code print at runtime?
function identity<T>(value: T): T {
return value;
}
const str = identity("hello");
console.log(str.toUpperCase());This code has a bug — what's wrong?
function logLength<T>(item: T): T {
console.log(`Length: ${item.length}`);
return item;
}
logLength("typescript");Complete the type parameter declaration so this identity function is generic over a type T, reusing the lesson's <T> syntax.
function identity<>(value: T): T { return value; } const num = identity(42); // T inferred as number
Put these lines in the correct order to define a generic Box<T> interface, then create a Box that holds a number — matching the lesson's example.
label: string;
const scoreBox: Box<number> = { label: "score", value: 99 };interface Box<T> {}
value: T;
Write a generic function called lastItem that accepts an array of any type and returns the last element, or undefined if the array is empty. Then write a generic interface Pair<T, U> that holds two values of potentially different types — first and second. Finally, create a Pair where first is a number and second is a string.
Try it live — edit the code and hit Run to see the output: