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:

TS union-version.ts
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());
tsc output
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:

TS overloads.ts
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));
Output
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:

TS hidden.ts
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);
tsc output
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:

TS date.ts
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());
Output
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 reverseText and reverseList.

Overloads shine in library code, where precise types for many call styles make a real difference to users.

Overloads in methods

TS method-overloads.ts
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());
Output
LIGHT

Passing a fallback guarantees a string result, so the caller doesn't need to handle undefined.

Try it yourself

Try it yourself
Console output