Generics
Write one function or type that works for many types without giving up checking.
What you'll learn
- Write a generic function whose return type depends on its argument
- Explain why a generic beats `any` for reusable code
- Constrain a type parameter with `extends`
Prerequisites
Explanation
Suppose you want a function that returns the first item of an array. Typed for strings it only works for strings. Typed with any it works for everything and checks nothing:
function firstAny(items: any[]): any { return items[0]; }
const n = firstAny([1, 2, 3]);
n.toUpperCase(); // no complaint — and a crash at runtime
The information that the array held numbers was thrown away. A generic keeps it.
A type parameter
function first<T>(items: T[]): T {
return items[0];
}
<T> declares a type parameter — a placeholder filled in per call, the way a normal parameter is filled with a value. Read the signature as: "for whatever type T the array holds, this returns that same T."
const a = first([1, 2, 3]); // a: number
const b = first(["x", "y"]); // b: string
You did not write first<number>(...). TypeScript infers the type argument from what you passed, which is why generics rarely feel heavy at the call site. You can pass it explicitly when inference cannot help: first<string>([]).
T is only a convention. <Item> is often clearer, and clarity wins over brevity in a signature others will read.
Constraints
Sometimes a generic must not accept literally anything. If your function reads .length, say so:
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "hi"); // fine — strings have length
longest([1, 2], [1, 2, 3]); // fine — arrays have length
longest(10, 20); // rejected — numbers have no length
extends here means "T must be assignable to this shape". It narrows what callers may supply while still preserving the specific type they used — longest("a","bb") still returns string, not { length: number }.
Generic types, not just functions
Interfaces and aliases take type parameters too:
interface Result<T> {
data: T;
error?: string;
}
const userResult: Result<{ name: string }> = { data: { name: "Ada" } };
You have already used generic types without naming them: Array<T> is exactly this, and Promise<T> is why await gives back the right type.
The rule of thumb
Reach for a generic when a function's output type depends on its input type. If it does not, a plain type is simpler and simpler is better.
Example
An unconstrained generic, a constrained one, and a generic interface — with types flowing through all three.
function first<T>(items: T[]): T {
return items[0];
}
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
interface Result<T> {
data: T;
error?: string;
}
const firstNumber = first([10, 20, 30]);
const firstWord = first(["alpha", "beta"]);
console.log(firstNumber + 1);
console.log(firstWord.toUpperCase());
console.log(longest("hello", "hi"));
console.log(longest([1, 2], [1, 2, 3]).length);
const wrapped: Result<string> = { data: "ok" };
console.log(wrapped.data.toUpperCase());Try it yourself
Call longest(10, 20) and Run. The constraint explains precisely why numbers are not allowed.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write a generic function `lastItem<T>(items: T[]): T` returning the final element of the array.
Checks: lastItem is defined · Returns the last number · Returns the last string
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write `describeLength<T extends { length: number }>(value: T): string` returning `"length 5"` for a value whose length is 5. It must accept strings and arrays but reject numbers.
Checks: describeLength is defined · describeLength("hello") returns "length 5" · describeLength([1,2,3]) returns "length 3"
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Using `any` where a generic belongs. `any` discards the caller's type; a generic carries it through to the return value.
- Adding type parameters a function never uses. If `T` appears only once in the signature, it is probably not needed.
- Forgetting a constraint, then being surprised that `.length` is rejected — an unconstrained `T` really could be anything.
Knowledge check
Takeaway
A generic is a type the caller fills in, so a reusable function keeps the caller's specific type instead of erasing it to `any`.
Summary
Generics declare type parameters (`<T>`) that are usually inferred at the call site, letting one function serve many types with full checking. `extends` constrains what callers may supply while preserving their specific type.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.