Function Overloads
Some functions behave differently depending on what you pass them — and return different types as a result. Overloads let you list each supported way of calling a function separately, so callers get a precise return type instead of a vague union.
The problem overloads solve
Here's a function that accepts a string or an array and reverses it:
function reverse(input: string | number[]): string | number[] { return typeof input === "string" ? input.split("").reverse().join("") : [...input].reverse(); } const word = reverse("stressed"); console.log(word.toUpperCase());
error TS2339: Property 'toUpperCase' does not exist on type 'string | number[]'.
We know that passing a string gives back a string, but the signature doesn't say so. Every caller would need a check or assertion to use the result.
Writing overloads
An overloaded function has several overload signatures (no body) followed by one implementation signature with the body:
function reverse(input: string): string; function reverse(input: number[]): number[]; function reverse(input: string | number[]): string | number[] { return typeof input === "string" ? input.split("").reverse().join("") : [...input].reverse(); } const word = reverse("stressed"); const nums = reverse([1, 2, 3]); console.log(word.toUpperCase()); console.log(nums.map(n => n * 10));
DESSERTS [ 30, 20, 10 ]
Callers only see the overload signatures. When you call reverse("stressed"), TypeScript picks the first signature that matches and uses its return type: string.
The implementation signature is hidden
The implementation signature must be compatible with every overload, but it isn't itself callable from outside:
function reverse(input: string): string; function reverse(input: number[]): number[]; function reverse(input: string | number[]): string | number[] { return typeof input === "string" ? input.split("").reverse().join("") : [...input].reverse(); } const maybe: string | number[] = Math.random() > 0.5 ? "ab" : [1, 2]; reverse(maybe);
error TS2769: No overload matches this call.
A value that's "string or array" doesn't match either overload on its own. If you want to allow that call too, add a third overload: function reverse(input: string | number[]): string | number[];.
Different numbers of parameters
Overloads also describe functions whose arguments mean different things depending on how many there are:
function makeDate(timestamp: number): Date; function makeDate(year: number, month: number, day: number): Date; function makeDate(a: number, month?: number, day?: number): Date { return month !== undefined && day !== undefined ? new Date(a, month - 1, day) : new Date(a); } console.log(makeDate(2026, 3, 14).getDate()); console.log(makeDate(0).getUTCFullYear());
14 1970
Calling makeDate(2026, 3) with exactly two arguments is an error, because no overload takes two — even though the implementation technically could.
Order matters
TypeScript tries overloads from top to bottom and uses the first match. Put more specific signatures before more general ones; otherwise a broad signature near the top will "swallow" calls meant for a specific one below it.
Prefer simpler alternatives when they work
Overloads add maintenance cost: the body can't use the overload information, so you write the type logic twice. Before reaching for them, consider:
- A union parameter, when the return type doesn't depend on the input.
- Optional parameters, when the variations are just "with or without this argument".
- A generic (lesson 20), when the return type simply mirrors the input type.
- Two separate functions with clear names, like
reverseTextandreverseList.
Overloads shine in library code, where precise types for many call styles make a real difference to users.
Overloads in methods
class Store { private data = new Map<string, string>(); get(key: string): string | undefined; get(key: string, fallback: string): string; get(key: string, fallback?: string): string | undefined { return this.data.get(key) ?? fallback; } } const s = new Store(); const theme = s.get("theme", "light"); console.log(theme.toUpperCase());
LIGHT
Passing a fallback guarantees a string result, so the caller doesn't need to handle undefined.