Generics
Writing a function or type once so it works with whatever type you feed it, while still keeping that type checked.
What is it?
Suppose you write a function that just hands back whatever you gave it:
function identity(value: number): number { return value; }
That works for numbers, but not for strings — you'd have to write a nearly identical identity function for every type you want to support, or give up and type the parameter as any, which throws away all type checking in the process (a caller could pass a number in and get a string out, and TypeScript wouldn't complain).
What you actually want is: "whatever type you pass in, give me that exact same type back" — without giving up checking altogether. TypeScript lets you express that with a type variable, conventionally named T, written in angle brackets before the parameter list:
function identity<T>(value: T): T { return value; }
Now identity(5) is known to return a number, and identity("hi") is known to return a string — TypeScript figures out T fresh for each call, based on what you actually passed in. This pattern — writing code once that works across many types while keeping each specific use fully checked — is called a generic.
Explain like I'm 10
A generic function is like a mold for making boxes that's adjustable to whatever you put in it — put in a small ball, get a small box back; put in a large book, get a large box back. It's one mold, but it never mixes up sizes: you always get back a box that fits exactly what went in.
Examples
A generic identity function
function identity<T>(value: T): T {
return value;
}
const num = identity(5); // T is inferred as number
const str = identity("hello"); // T is inferred as string
const wrong: string = identity(5);
// Error: Type 'number' is not assignable to type 'string'.T is a placeholder for "whatever type is passed in." TypeScript figures out T separately for each call and uses it to check both the argument and how the result is used.
A generic function over arrays
function firstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstNum = firstItem([1, 2, 3]); // number | undefined
const firstName = firstItem(["Ana", "Bo"]); // string | undefined
// Generics can also be constrained:
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hi", "hello"); // works — strings have .length
longest(3, 4);
// Error: number doesn't satisfy the constraint '{ length: number }'.firstItem works for an array of any type. The extends clause on longest restricts T to types that actually have a .length property, so calling it with plain numbers is rejected.
How it works
When you call a generic function, the compiler looks at the actual argument(s) you pass and works backward to figure out what T must be — this is called type inference for generics. It then substitutes that inferred type everywhere T appears in the signature, and checks the rest of the call as if you'd written that specific type by hand. You can also specify T explicitly, like identity<string>("hi"), if you want to be precise or if inference can't figure it out on its own. A constraint (T extends SomeShape) narrows what T is allowed to be, so the function body can safely rely on T having certain properties.
Why does it exist?
Without generics, you'd face a trade-off: either duplicate the same logic for every type you need to support (a numberIdentity, stringIdentity, and so on), or use any and lose type checking entirely. Generics let you write the logic exactly once, while still getting full, specific type checking for every individual call.
When to use it
Reach for generics whenever a function or type's logic doesn't actually depend on a specific type — a function that stores, returns, wraps, or transforms whatever it's given (arrays, containers, caches, API wrappers) is a natural fit. Also reach for them when writing a reusable interface, like a generic ApiResponse<T> that wraps different kinds of data.
When not to use it
If a function only ever needs to work with one specific type, adding a type variable is unnecessary complexity — just write the concrete type directly. Generics are for genuinely type-independent logic, not a default to sprinkle onto every function.
Common mistakes
Adding a generic
<T>to a function that never actually usesTin its parameters or return type — ifTisn't tied to anything, it isn't doing any useful checking.Reaching for
anyinstead of a generic when the goal is really "any type, but consistently the same type throughout," which a generic captures andanydoesn't.Forgetting a constraint (
extends) and then trying to use a property inside the function body that not every possibleTactually has.
Practice exercises
- Easy:
Write a generic function
wrapInArray<T>(value: T): T[]that returns a one-element array containing the value. - Medium:
Write a generic function
pluck<T>(items: T[], index: number): T | undefinedthat safely returns the item at a given index. - Hard:
Write a generic function
merge<T extends object, U extends object>(a: T, b: U): T & Uthat merges two objects and returns their intersection type.
Interview questions
What problem do generics solve that writing a separate function per type would also solve, but worse?
They let you write the logic exactly once while still getting full, type-specific checking for every call, instead of duplicating nearly identical functions like numberIdentity and stringIdentity for each type you need to support.
Why is `any` a worse alternative to a generic for a function like `identity`?
any disables checking entirely — a caller could pass a number and treat the result as a string with no error — whereas a generic ties the return type to whatever specific type was actually passed in.
Given `function identity<T>(value: T): T { return value; }`, what type is inferred for `identity(5)`, and how does TypeScript arrive at it?
number — TypeScript works backward from the actual argument 5 to figure out what T must be for that specific call, then substitutes number everywhere T appears in the signature.
Why does `const wrong: string = identity(5);` fail to compile?
T is inferred as number from the argument 5, so identity(5) returns number, which isn't assignable to a variable explicitly typed string.
How does TypeScript infer a generic type variable when you call the function without specifying it explicitly?
It looks at the actual argument(s) supplied at the call site and works out the most specific type that makes the call valid, using that as T for just that call.
How do you explicitly specify a generic type argument instead of relying on inference, and when might you need to?
By writing it in angle brackets at the call site, like identity<string>("hi") — useful when inference can't determine a specific-enough type on its own, or when you want to be explicit for clarity.
What does adding a constraint like `T extends { length: number }` do to a generic type parameter?
It narrows the set of types T is allowed to be to only those that structurally have a .length property, letting the function body safely use .length while still accepting any type that qualifies.
Why does `longest(3, 4)` fail to compile when `longest` is declared as `function longest<T extends { length: number }>(a: T, b: T): T`?
number doesn't have a .length property, so it doesn't satisfy the constraint — the constraint restricts T to types with .length, and plain numbers aren't one of them.
What's wrong with a generic function whose type parameter `T` never actually appears in its parameters or return type?
If T isn't tied to any input or output, the compiler has no argument to infer it from and no way to check anything against it, so it isn't doing any useful type checking — it's generic in name only.
Given `function firstItem<T>(items: T[]): T | undefined`, what's the inferred type of `firstItem(["Ana", "Bo"])`?
string | undefined — T is inferred as string from the array's element type, and the | undefined in the declared return type accounts for the case where the array is empty.
Why does `firstItem`'s return type include `| undefined` even though the function body doesn't check the array's length before indexing?
TypeScript's array indexing doesn't automatically add undefined for out-of-bounds access by default, so | undefined here is a deliberate, hand-written part of the signature to represent the empty-array case honestly, rather than something the compiler infers on its own.
What's the difference between constraining a generic with `T extends SomeShape` and simply typing a parameter as a union?
A constraint restricts which types `T` may be while still preserving and returning that exact specific type per call; a union parameter widens the parameter to a fixed set of types and any return value tied to it is only known as that same wide union, not the caller's specific type.
Can generics be used on interfaces and type aliases, not just functions?
Yes — a reusable shape like interface ApiResponse<T> { data: T; error?: string } uses the same type-variable mechanism to describe a wrapper whose contents vary by use, while the wrapper's own shape stays consistent.
Why doesn't `any` capture the idea of "any type, but the same type consistently throughout one call" the way a generic does?
any disables checking for every use of that value independently, so nothing enforces that, say, the input and output stay the same type — a generic ties every occurrence of T in one call to a single, consistent, inferred type.
What could go wrong with `function merge<T, U>(a: T, b: U): T & U` if it's called with two primitives, like `merge(1, "a")`?
Without constraints, T and U can be anything, including incompatible primitives — intersecting number & string produces never, so the function's return type becomes unusable for that call even though it compiles.
How does adding `T extends object, U extends object` to `merge` prevent that problem?
It restricts both type parameters to object types, ruling out primitives entirely, so the intersection T & U is always between two object shapes that can be sensibly merged.
When would you prefer a generic function over writing several function overloads, one per accepted type?
When the logic itself doesn't change based on the type — generics express that once with a single implementation, while overloads are better suited to genuinely different behavior or return types per input type.
Why is the minimal `identity<T>(value: T): T` function a useful example for understanding generics, despite doing so little?
It isolates the core mechanism — a type variable flowing from parameter to return type — without any other logic getting in the way, making it clear that T is inferred per call and preserved through the signature.
What happens if you call `identity<string>(5)`, explicitly specifying `T` as `string`?
It's a compile error — explicitly specifying T doesn't override the argument's actual type, it just fixes what T is expected to be, and the compiler still checks that 5 (a number) is assignable to that fixed T.
Does specifying a generic type argument explicitly skip the compiler's checking of the argument against it?
No — explicit type arguments fix T for that call, but the compiler still verifies that each actual argument is assignable to the resulting, now-concrete parameter types.
Why might inference occasionally fail to pin down a specific-enough type, forcing you to specify `T` explicitly?
Some call sites are ambiguous on their own — for example, calling a generic function with an empty array literal gives the compiler no element to infer an element type from, so it may fall back to an overly wide or unhelpful inferred type.
Does `T extends { length: number }` require the argument to be declared as implementing some specific interface, or just to structurally have a `.length` property?
Just structurally — TypeScript's structural typing means any type with a compatible .length: number property satisfies the constraint, regardless of its name or where it's declared.
What does a generic type parameter default, like `interface Box<T = string>`, do?
It supplies a fallback type to use for T when the type is referenced without an explicit type argument (e.g. plain Box behaves like Box<string>), while still allowing Box<number> or any other type to be specified.
Compare `function f<T extends string | number>(x: T): T` with `function f(x: string | number): string | number` — what's the key behavioral difference?
The generic version returns the caller's exact specific type (passing in a string gives back a string), while the union version always returns the wide string | number type regardless of what was actually passed in, losing that specificity.
Why does using a generic parameter preserve a value's specific type through a function call, where widening to a union or `any` would lose it?
A generic ties the return type directly to the inferred T from that particular call's argument, so the compiler carries the exact type through; a union or any return type is fixed and identical for every call, discarding whatever specific type came in.
How would you write a generic function that reverses an array while keeping full type safety?
As function reverse<T>(items: T[]): T[], so calling it with number[] returns number[] and calling it with string[] returns string[] — the element type flows through instead of being widened or lost.
Why is a generic container type, like a `Box<T>` that stores and later returns a value, preferable to using `any` for the stored value?
With any, nothing stops storing one type and retrieving it as another; with Box<T>, the type used when constructing the box is remembered and enforced consistently every time the value is read back out.
What's the relationship between a generic function's type parameter and the concept of type inference specifically?
Type inference is the mechanism that determines what a generic's type parameter actually is for a given call, based on the arguments passed — without inference, every generic call would require tediously specifying the type argument by hand.