TSadvancedts-evergreen

Function overloads

Many faces, one body.

Overload signatures let one function advertise several distinct call shapes β€” different argument types mapping to different return types β€” backed by a single implementation signature that callers never see.

Overload signatures vs the implementation signature

An overloaded function is written as two or more overload signatures (just headers, no body) followed by one implementation signature that actually has the body. Callers only ever see and resolve against the overload signatures β€” the implementation signature is invisible from outside. TypeScript picks the first overload whose parameters match the call, and that overload's return type is what the call site gets.

When overloads beat a union return

A plain (x: string | number) => string | number says nothing about the relationship between input and output β€” every call returns the wide union, so you cannot use the result without re-narrowing it. Overloads encode the correlation: string in gives string out, number in gives number out. The caller gets a precise return type for free, no cast, no if.

The implementation must be a supertype of every overload

The implementation signature has to be assignable-from every overload it covers β€” its parameters wide enough to accept all the overloads' arguments, its return wide enough to cover all their returns. It is usually written with unions (string | number) or ...args: any[], and the body inspects the arguments at runtime with typeof / Array.isArray to decide what to do. That implementation type is never used for overload resolution, so it can be loose.

Overloads vs a single union parameter

Reach for overloads when the count or types of parameters change between call shapes, or when return type depends on which arguments were passed. Reach for a single signature with a union parameter when every call has the same arity and the body treats the union uniformly. Overloads are heavier to maintain β€” every new shape is another header β€” so prefer the union unless the input/output correlation genuinely needs them.

Try it 4 examples

parse: return type follows the argument type

TSintro
Press Run to execute

The output {"n":42,"s":"42","doubled":84,"shouted":"42"} shows each call landed on the matching overload β€” parse('42') returned the number 42 and parse(42) returned the string "42" β€” so n * 2 and s.toUpperCase() both typecheck with no cast or guard.