Generics
Learn TypeScript generics: generic functions, interfaces, and classes, constraints with extends, default type parameters, and utility types.
Introduction
Generics let you write a function, interface, or class once, and have it work correctly and safely across many different types — without giving up type checking by falling back to `any`.
- Why generics exist and the problem they solve compared to using any.
- How to write and call a generic function.
- How to define generic interfaces and classes.
- How to constrain a generic type parameter with extends.
- The most common built-in utility types (Partial, Pick, Omit, Record).
Why Generics?
Imagine a function that returns whatever value you pass into it. Typed as `any`, it would compile, but you'd lose all type information — calling it with a string and getting back `any` means the compiler can no longer help you. A generic type parameter, conventionally named `T`, lets the function's input type flow through to its output type, fully preserved.
Generic Functions
function identity<T>(value: T): T { return value;}
const num = identity(42); // inferred as numberconst str = identity("hello"); // inferred as stringGeneric Interfaces and Classes
Interfaces, type aliases, and classes can all take type parameters too, letting a single reusable shape or implementation adapt to whatever type it's used with.
interface Box<T> { value: T;}
class Stack<T> { private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }}
const numberStack = new Stack<number>();numberStack.push(1);numberStack.push(2);console.log(numberStack.pop());Click Run to see what this code prints.
Constraints with extends
Sometimes a generic function needs to guarantee its type parameter has certain properties — for example, that it has a `.length`. Adding `extends` to the type parameter constrains which types are allowed, while still letting the specific type vary.
function longest<T extends { length: number }>(a: T, b: T): T { return a.length >= b.length ? a : b;}
console.log(longest("hi", "hello")); // "hello"console.log(longest([1, 2], [1, 2, 3])); // [1, 2, 3]Default Type Parameters
A generic type parameter can have a default, used whenever the caller doesn't explicitly supply one — similar in spirit to default function parameters.
interface ApiResponse<T = unknown> { data: T; success: boolean;}
const response: ApiResponse<{ id: number }> = { data: { id: 1 }, success: true };Common Utility Types
TypeScript ships several built-in generic utility types that transform an existing type instead of making you write it by hand.
| Utility Type | What It Does |
|---|---|
| Partial<T> | Makes every property of T optional. |
| Required<T> | Makes every property of T required, even if originally optional. |
| Readonly<T> | Makes every property of T readonly. |
| Pick<T, K> | Builds a new type using only the listed keys K from T. |
| Omit<T, K> | Builds a new type with the listed keys K removed from T. |
| Record<K, V> | Builds an object type with keys K, each mapped to value type V. |
Common Mistakes
- Reaching for `any` in a reusable function instead of a generic, silently throwing away type information the caller actually needs.
- Accessing properties on a generic `T` without constraining it with `extends`, then being confused why the compiler rejects it.
- Adding a generic type parameter to a function or class that never actually varies — a sign the generic wasn't needed at all.
- Confusing a generic type parameter (a compile-time placeholder like `T`) with a regular runtime function parameter.
Best Practices
- Reach for a generic whenever a function or class's logic is identical regardless of the specific type involved — a stack, a cache, a fetch wrapper.
- Constrain generics with `extends` as tightly as the function genuinely needs, and no tighter.
- Use plain names like `T`, `K`, `V` for simple, well-understood generics; use descriptive names like `TItem` for complex, multi-parameter generics.
- Learn the built-in utility types (Partial, Pick, Omit, Record) before writing custom equivalents from scratch.
Frequently Asked Questions
No — any throws away all type information, while generics preserve and propagate it through the function or class so the compiler can still fully check your code.
Yes — for example, function pair<A, B>(a: A, b: B): [A, B] takes two independent type parameters.
No — like all TypeScript types, generic type parameters are fully erased. The emitted JavaScript is identical to what you'd write without them.
T is a single value of the generic type; T[] is an array of values of that type — both appear constantly in generic function signatures.
Key Takeaways
- Generics let a function, interface, or class stay reusable across many types without losing type safety.
- A generic type parameter (commonly T) is a placeholder filled in at each call site.
- extends constrains a generic to types with certain properties, without pinning it to one exact type.
- Built-in utility types like Partial, Pick, Omit, and Record cover most everyday type transformations.
- Generics are fully erased at compile time and add zero runtime overhead.
Summary
You can now write reusable, fully type-safe functions, interfaces, and classes with generics. Next, you'll learn enums — TypeScript's way of naming a fixed, related set of constant values.