Generics Basics
Introduction
Generics let you write functions, classes, and types that work with many types while keeping compile-time safety. Without them, you either duplicate code for each type or fall back to any and lose checking. This chapter introduces generic functions, interfaces, and classes—the core tool for reusable typed APIs.
Prerequisites
- Classes and Access Modifiers
- Type Aliases vs Interfaces
- Comfort with functions and interfaces
The Problem Generics Solve
function first(arr: any[]): any {
return arr[0];
}
const n = first([1, 2, 3]);
const s = first(["a", "b"]);
// n and s are any—no autocomplete or errors on misuseWith a generic:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = first([1, 2, 3]);
const str = first(["a", "b"]);Code explanation:
Tis a type parameter filled in when you callfirstnumisnumber | undefined;strisstring | undefined
Generic Functions
Syntax: type parameter in angle brackets before parameters.
function wrap<T>(value: T): { value: T } {
return { value };
}
const boxed = wrap("hello");Explicit type argument (optional when inference works):
const boxedNumber = wrap<number>(42);Code explanation:
- Usually TypeScript infers
Tfrom arguments - Specify
<number>when inference fails or you need a specific type
Multiple type parameters
function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const p = pair("id", 1);p is [string, number].
Generic Interfaces
Code explanation:
Tstands for the payload type- One interface definition serves all response data shapes
Generic Classes
Code explanation:
Stack<number>only accepts numbers inpushpopreturnsnumber | undefined
Generic Type Aliases
type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
function ok<T>(value: T): Result<T> {
return { success: true, value };
}Default type parameters appear in the next chapter; here E defaults to Error.
Inference in Generic Calls
function map<T, U>(items: T[], fn: (item: T) => U): U[] {
return items.map(fn);
}
const lengths = map(["a", "bb"], (s) => s.length);T is string, U is number, result is number[].
Generics vs any
any | Generic <T> | |
|---|---|---|
| Type checking | Off for that value | Preserved per call |
| Relationships between args/returns | Lost | Maintained |
| Refactor safety | Low | High |
Prefer generics for containers, utilities, and API wrappers.
Practical Example: Simple Cache
extends on TKey is a constraint (next chapter).
Rules of Thumb
- Name type parameters clearly:
T,TItem,TKey,TValue—not single letters only in large APIs. - Start without generics; add them when you see duplicated types or
any. - Let inference work; add explicit
<T>at call sites when errors are unclear.
FAQ
How many type parameters is too many?
If you have more than two or three, consider an options object type instead.
Can generics be recursive?
Yes, for example type Tree<T> = { value: T; children: Tree<T>[] }—used in advanced structures.
Do generics slow down runtime?
No. They are erased at compile time—no runtime type parameter in standard JS output.
Generic function vs overload?
Generics unify one implementation. Overloads document distinct call shapes—pick based on whether behavior is the same after narrowing.
Why T[] instead of Array<T>?
Same meaning; style preference. Be consistent in your project.
Can React components be generic?
Yes (function List<T>(props: { items: T[] }) { ... }), with some syntax quirks in TSX—framework docs help when you reach UI code.