Generics
Functions over types — the step where TypeScript starts paying for itself.
40 minDifficulty 3/5typesAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A function that wraps a value in an array works the same way regardless of whether the value is a number, a string, or a User object — but writing it three times, once per type, or writing it once with `any` and losing all type safety, are both worse than writing it once in a way that stays type-safe for whatever type it's actually called with. That's what a generic is for.
The mental model
A generic is a type parameter — a variable, but for types instead of values. `function wrap<T>(value: T): T[]` says "this function works for ANY type T, and whatever T you give it, you get exactly that same T back out, in an array." The compiler substitutes the real type at each call site and checks everything against that specific substitution.
How it works
Generics preserve the relationship between input and output types
`function first<T>(arr: T[]): T` guarantees that calling it with a `string[]` returns a `string`, and calling it with `number[]` returns a `number` — the SAME type parameter appearing in both the input and output is what expresses that relationship. Writing this with `any` instead would compile fine but tell the caller nothing about what type comes back.
Type inference usually makes explicit type arguments unnecessary
Calling `first([1, 2, 3])` doesn't require writing `first<number>([1, 2, 3])` — the compiler infers `T` as `number` from the argument you actually passed. Explicit type arguments (`first<number>(...)`) are needed only when the compiler genuinely can't infer the type from context, which is the less common case.
Constraints narrow what a generic type is allowed to be
`function getLength<T extends { length: number }>(item: T): number` restricts T to any type that has a `.length` property — strings, arrays, and custom objects with a length field all qualify, but a plain number doesn't. This lets you use `.length` inside the function body safely while keeping the function generic over everything that actually has one.
Multiple type parameters can relate several types together at once
`function pair<A, B>(a: A, b: B): [A, B]` tracks two independent types simultaneously, so calling `pair(1, 'x')` correctly infers a return type of `[number, string]` — each type parameter is substituted independently based on the corresponding argument, and the relationships between them (here, forming a tuple) are enforced by the return type's shape.
The mechanism
At each call site, the compiler examines the arguments actually passed and infers a concrete type for each generic type parameter — substituting that concrete type everywhere the parameter appears in the function's signature, including the return type. The function body is checked once, generically, against the constraint (if any) placed on the type parameter, rather than being re-checked separately for every possible concrete type.
What people get wrong
- Generics and `any` accomplish basically the same goal of handling multiple types.
- `any` disables type checking entirely, losing all information about the type; a generic PRESERVES the specific type through the function, so the caller gets back a correctly-typed result specific to what they passed in, with full checking intact. This is the entire point of generics — a function typed with `any` compiles but tells you nothing useful about what comes out, while a generic version gives you exactly the right type back, checked.
- You always need to explicitly specify the type argument, like `first<number>(arr)`.
- TypeScript infers the type parameter from the arguments you actually pass in the vast majority of cases — explicit type arguments are needed only when inference genuinely can't determine the type from context, such as calling a generic function with no arguments that reference the type. Writing explicit type arguments everywhere out of habit is unnecessary verbosity in most real code, since inference already does the work correctly.
- A generic without a constraint means the type could be literally anything, so you can't do anything useful with it inside the function.
- An unconstrained generic can still be passed through, stored, or returned — what you CAN'T do is call methods or access properties specific to some type, unless a constraint guarantees they exist. Constraints exist precisely to unlock safe, specific operations while staying generic. Understanding this distinction is what makes it clear why adding `extends { length: number }` (or similar) to a generic parameter is necessary the moment the function body needs to use that property.
When not to use it
- A function's behaviour genuinely differs based on the specific type, not just its shape.
- Function overloads or a discriminated union, rather than forcing one generic implementation to branch internally on the runtime type — generics are for code that behaves identically regardless of the specific type substituted.
- You only ever call a function with one specific, known type and never intend to reuse it for others.
- A plain, concretely-typed function — adding a generic type parameter that's only ever instantiated one way adds complexity with no actual reuse benefit.
Terms
- Type parameter
- — A placeholder for a type, conventionally written as a single capital letter like T, substituted with a concrete type at each call site.
- Type inference
- — The compiler automatically determining a generic type parameter's concrete type from the arguments passed, without requiring it to be written explicitly.
- Generic constraint
- — An `extends` clause on a type parameter restricting it to types that satisfy a given shape, enabling safe use of properties or methods that shape guarantees.
- Instantiation
- — The act of substituting a concrete type for a generic type parameter at a specific call site or usage.
In an interview
Why does using `any` instead of a generic type parameter lose type safety, even if the function 'works' the same way at runtime?
- at runtime, `any` and a generic function behave identically — types don't exist at runtime either way
- the difference is entirely at compile time: `any` tells the compiler nothing about the relationship between input and output types
- a generic preserves that relationship, so the caller's specific type is checked correctly on the way out, not just accepted blindly on the way in
Can you recall it?
Why does a generic function like `first<T>(arr: T[]): T` preserve type information that a version written with `any` would lose?