Lesson 47 of 80
TypeScript doesn't pick the best-matching overload — it picks the first one, top to bottom, that the arguments happen to fit. Order the signatures wrong and the wrong one wins silently.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The function-overloads lesson covers the split between the declared overload signatures callers see and the wider implementation signature underneath. This lesson is about the specific algorithm TypeScript uses to pick which declared overload applies when a call could plausibly match more than one: it checks them top to bottom, in the order you wrote them, and stops at the first one the arguments are assignable to.
That's a meaningfully different rule than languages like Java or C#, which pick the most specific matching overload regardless of where it's declared. TypeScript does no such comparison — first match wins, full stop, even if a later overload would have been a tighter fit.
function pickup(x: string | number): string;
function pickup(x: number): number;
function pickup(x: string | number): string | number {
return typeof x === "number" ? x * 2 : x.toUpperCase();
}
const result = pickup(5);
// result: string — not number!
5 is a number, and it's assignable to string | number — the first overload — so TypeScript stops there and never even considers the second, more specific number overload below it. The call still runs correctly at runtime (the implementation's typeof check handles it fine), but the type callers see is wrong: result is inferred as string, and nothing before this line warns you. Swapping the two declarations fixes it completely:
function pickup(x: number): number; // ✅ more specific, now checked first
function pickup(x: string | number): string;
function pickup(x: string | number): string | number {
return typeof x === "number" ? x * 2 : x.toUpperCase();
}
const result = pickup(5); // result: number — correct
Nothing about the implementation changed — only the declaration order. That's the entire bug and the entire fix.
Given the pickup example above, "what type does result get, and why?" is a sharp diagnostic question — it's easy to assume TypeScript is smart enough to find the tightest-fitting overload, and the honest answer (it isn't; it's first-match, top to bottom) is the kind of detail that only shows up once you've actually been bitten by it.