When modelling a TypeScript function whose result type depends on its argument, when would you reach for a type parameter rather than writing a set of overload signatures?
answer
- a rule versus a list
- open set of types you never named
- two positions that must agree
- what happens when the argument is a union
- one signature to maintain, not three
basics
~20 sUse a type parameter when one uniform rule relates argument to result across an open set of types, and overloads only when a small fixed set of unrelated argument shapes produce unrelated results. Overloads also break on union arguments and on unlisted types.
solid answer
~50 sA type parameter expresses a *rule*: the result is the element type of the array, or the same type that came in. Overloads express an *enumeration*: this exact input shape yields that exact output. So if the relationship is uniform and the set of types is open, use the type parameter — it works for types you never anticipated, and there is one signature to maintain. Reach for overloads only when the accepted shapes are genuinely few and unrelated, or when the arity itself differs between calls. Two practical costs push against overloads: a call whose argument is typed as a union of two overloaded parameter types is a compile error, because resolution picks exactly one signature and does not distribute over the union; and each added case is another signature to keep consistent by hand.
code
typescript · 13 lines// Enumeration: only the listed shapes are callable
function len(x: string): number;
function len(x: unknown[]): number;
function len(x: string | unknown[]): number {
return x.length;
}
// Rule: correct for every element type, including ones you never saw
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // number | undefinedgo deeper
Know that both let one function serve several argument types, and that a type parameter covers types the author never listed while an overload set covers only the listed ones.
Explain that overload resolution matches exactly one listed signature, so a union-typed argument fails, and that the implementation signature is invisible to callers.
Argue the choice from maintenance and consumer experience: one rule checked once against the body versus a list that must be kept in step by hand, plus the narrowing burden overloads push onto callers.
Own it as a public-API decision — the encoding you publish determines whether adding a supported type is a one-line change for you or a breaking chore for every consumer, and how legible the surface stays over years.
## The two tools Both mechanisms let one function present different result types to different callers, but they encode different things. An **overload set** enumerates. You list a fixed number of externally visible signatures, and every call must match one of them: ```ts function len(x: string): number; function len(x: unknown[]): number; function len(x: string | unknown[]): number { return x.length; } ``` A **type parameter** states a relation that holds for all types at once: ```ts function first<T>(items: T[]): T | undefined { return items[0]; } ``` `first` was never told about `User`, `Date` or a type your consumer will define next year, yet it is correct for all of them. That is the difference in one sentence: a type parameter is closed under types you have not met. ## When the type parameter wins Reach for the type parameter when you can state the rule in words without naming any concrete type: *the result is the element type of the array*, *the result is what the callback returned*, *the value comes back unchanged*. Wrappers, containers, collection helpers and pass-through utilities are almost always this shape. Signs you are in this territory: - You find yourself writing overloads whose bodies would be identical. - The overload list would need a new entry every time a caller uses a new type. - Two positions in the signature must agree with each other; the type parameter is the only thing that can *link* them, and no number of overloads expresses "these two are the same type" for an open set. ## When overloads still win Overloads are the honest answer when the cases are genuinely discrete and unrelated: - **Different arity.** `f(a)` and `f(a, b, c)` mean different things and return different types. A type parameter has nothing to say about how many arguments there are. - **A handful of unrelated concrete shapes.** Two or three input types whose outputs follow no shared rule — the enumeration *is* the specification, and writing it out is clearer than encoding it in the type system. - **A published, readable API surface.** An overload list shows a consumer exactly the supported call forms in their editor, one per line, which can beat a single clever generic signature nobody can read. ## The union-argument trap This is the failure most worth knowing, because it surfaces in real code rather than in a tutorial. Overload resolution picks exactly one signature; it does not distribute over a union argument: ```ts declare const value: string | unknown[]; // len(value); // Error: No overload matches this call. ``` Both branches would individually be fine, but the union matches neither listed signature, and the implementation signature is not callable from outside. The caller is forced to narrow — or to widen your API. A single non-overloaded signature accepting `string | unknown[]` has no such problem, and neither does a generic function whose parameter type is satisfied by the union. ## The maintenance asymmetry An overload set has an implementation signature that is *not* visible to callers and is *not* checked against the overloads with any real rigour. The compiler permits an implementation signature that is looser than the list above it, so a mismatch between what you promised and what you implemented can survive compilation. Every new case means editing the list, the implementation signature and the body, keeping three things in step by hand. A generic signature has one place to change, and the body is checked once against the type parameter for all callers. ## They are not mutually exclusive Overload signatures can themselves be generic, so a function can offer two arities where each is generic in its element type. Use that when arity genuinely varies but the per-arity relationship is still a rule. ## How to decide, in review Ask the author to state the return type in one sentence without naming a specific type. If they can — "it gives back whatever the callback returned" — the type parameter is the right encoding and the overload list is an under-specified approximation of it. If they cannot, and the sentence turns into "if you pass a string you get a number, and if you pass a date you get a boolean", the cases really are unrelated and enumeration is honest. Then check the union case explicitly: if consumers plausibly hold a value typed as the union of your parameter types, an overload set will make their life worse, and that alone often settles it.
- What happens when you call an overloaded function with an argument typed as a union of two overloaded parameter types?It fails to compile with "No overload matches this call". Resolution selects exactly one signature and does not distribute over the union, and the implementation signature is not callable from outside. The caller must narrow first, or you must add a signature that accepts the union explicitly.
- Can an overload signature itself be generic?Yes. Each overload in the list may declare its own type parameters, which is the usual way to offer several arities that are each generic in their element type. You still pay the enumeration costs — one signature per call form and an implementation signature to keep in step — so use it only when arity genuinely varies.
- If the generic version is more general, why not always prefer it?Generality is not readability. When the accepted forms are two or three unrelated concrete shapes, an overload list documents them plainly in a consumer's editor, one line per supported call. A signature general enough to cover unrelated cases usually needs type-level machinery that costs far more to read than it saves.
saying these in an interview costs you the question
- Says overloads and generics are interchangeable styles
- Thinks a union argument is split across matching overloads automatically
- Believes the implementation signature is callable by consumers
- Adds an overload per concrete type instead of stating the rule once
- Claims overloads produce different runtime functions