Menu

TypeScript Function Overloading: Overload Signatures

TypeScript function overloads let one function have several call signatures, each with its own return type. Learn the overload signatures plus implementation pattern, the rules the compiler checks, when a union parameter is better, and overloads in classes.

This page includes runnable editors - edit, run, and see output instantly.

Function overloading in TypeScript means writing several call signatures for one function, followed by a single implementation. Each signature can pair different parameter types with a different return type, and the caller gets the precise one.

Without the overloads, parse would return number | number[] for every call, and one + 1 would be an error until you narrowed the result yourself.

Overload Signatures and the Implementation

An overloaded function has two parts:

  1. Overload signatures: declarations with no body, one per supported call shape. These are the only signatures callers can use.
  2. The implementation signature: the last declaration, with the body. Its parameters must accept everything the overloads accept, and its return type must cover every overload's return type. It is invisible from outside.

Types exist only at compile time, so there is one JavaScript function at run time. The implementation has to inspect its arguments (typeof, Array.isArray, arguments.length...) to decide what to do. The compiler checks that the overloads and the implementation agree:

function format(value: string): string;
function format(value: number): number {
  return value;
}
// error TS2394: This overload signature is not compatible with its implementation signature.

The fix is to widen the implementation: function format(value: string | number): string | number.

The Implementation Signature Is Not Callable

This is the rule that surprises people most. A call has to match one of the overload signatures on its own; TypeScript does not combine them.

The compiler prints:

index.ts(12,19): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
      Type 'string' is not assignable to type 'string[]'.

The implementation accepts string | string[], but callers cannot see it. Add a third overload that takes the union and returns the union, and the call compiles and prints [ 1, 2 ]:

function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[];
function parse(input: string | string[]): number | number[] {
  return Array.isArray(input) ? input.map(Number) : Number(input);
}

Different Numbers of Parameters

Overloads also describe calls with different arities. Here a date can be built from a timestamp or from year, month and day, but not from two numbers:

A single signature with two optional parameters would accept makeDate(2024, 3) and silently build the wrong date. The overloads turn that into a compile error (TS2575).

Order Matters

TypeScript tries the overloads from top to bottom and picks the first one that matches. Put the most specific signatures first. A broad overload early in the list swallows the calls meant for the ones after it:

function describe(value: unknown): string;   // matches everything
function describe(value: string): "text";    // never chosen
function describe(value: unknown): string {
  return typeof value === "string" ? "text" : "other";
}

const d = describe("hi"); // d: string, not "text"

Swap the first two signatures and describe("hi") is typed "text".

Overloads or a Union Parameter?

Overloads are worth their extra lines when the return type depends on the argument types. When it does not, one signature with a union parameter is shorter, easier to read, and accepts union arguments that overloads would reject.

UseWhen
A union parameterSame return type for every input
Optional parametersThe call shapes differ only by trailing arguments that can be left out freely
OverloadsThe return type changes with the arguments, or some argument combinations must be rejected
A genericThe return type is built from the argument type, such as identity<T>(x: T): T

A generic with a conditional type can express some overload sets as one signature, but for two or three cases overloads are usually easier to read.

Overloaded Methods and Constructors

Methods use the same pattern inside a class: overload signatures, then the method with a body. Constructors can be overloaded the same way.

Interfaces and object types can declare overloads too, as several call signatures or several method signatures with the same name. Many built-in functions are declared this way: hover over reduce on an array in an editor and it shows "+2 overloads".

Frequently Asked Questions

Does TypeScript support function overloading?

Yes, at the type level. You write several overload signatures (declarations without a body) followed by one implementation. Callers see only the overload signatures. There is still just one JavaScript function at run time, so the implementation checks the arguments itself and handles every case.

What does "No overload matches this call" mean?

Error TS2769: the arguments do not fit any of the overload signatures. The implementation signature does not count, so a call with a union argument such as string | string[] fails even when the implementation accepts it. Add an overload that takes the union, or replace the overloads with one signature.

When should I use overloads instead of a union type?

Use overloads when the return type depends on which argument types are passed, for example string in gives number out but string[] in gives number[] out. When the return type is the same for every input, a single signature with a union parameter is simpler and also accepts union arguments.

Can arrow functions be overloaded in TypeScript?

Not with the overload declaration syntax, which only works for function declarations and methods. You can give a variable an overloaded type with several call signatures, type Parse = { (s: string): number; (s: string[]): number[] }, but assigning an arrow function to it usually needs a type assertion, so a function declaration is the cleaner choice.

Why is my overload signature not compatible with its implementation signature?

Error TS2394 means one overload accepts or returns something the implementation does not. The implementation's parameters must accept every overload's parameters, and its return type must be compatible with every overload's return type. Widening the implementation (often to a union) fixes it.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED