TypeScript Tutorial
TypeScript Function Overloading
Same name, different call signatures. The implementation covers every overload the compiler listed.
One name, several call shapes
Overloading means you list more than one call signature for the same function name. Each signature is a promise to the caller: these argument types, this return type. Then you write one implementation that covers every signature the compiler listed.
That is useful when the work is the same idea — add two values, compute an area — but the inputs are not the same type or the same count. You keep one name instead of addNumber and addText.
Two signatures, one body
Write each overload as a line that ends with a semicolon and has no braces. Those lines are call signatures. The last function with that name is the implementation: it has a body, and its parameter types must be wide enough for every overload.
Example
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number | string, b: number | string): number | string {
if (typeof a === "number" && typeof b === "number") {
return a + b;
}
return String(a) + String(b);
}
console.log(add(2, 3));
console.log(add("go", "ld"));add(2, 3) matches the first signature and is a number. add("go", "ld")matches the second and is a string. A mixed call such as add(2, "x") does not match either signature, so tsc rejects it even though the implementation could concatenate.
Click Try it in TypeScript under the example. That opens /typescript/try. Change the arguments and compile again.
Different number of parameters
You can also overload by count. area with one argument is a square. area with two arguments is a rectangle. The implementation uses an optional parameter so both call shapes fit in one body.
Example
function area(side: number): number;
function area(width: number, height: number): number;
function area(width: number, height?: number): number {
if (height === undefined) {
return width * width;
}
return width * height;
}
console.log(area(4));
console.log(area(4, 6));The compiler does not guess. It counts the arguments you passed and checks them against the call signatures, not against the implementation’s optional height?.
How the compiler picks
Matching uses the call signatures. The implementation is hidden from the call site. Callers only see the overload list.
| Call | Matches | Result type |
|---|---|---|
add(2, 3) | add(number, number) | number |
add("go", "ld") | add(string, string) | string |
area(4) | area(number) | number |
area(4, 6) | area(number, number) | number |
If two overloads could both work, tsc reports an ambiguous call. Fix it by passing a value whose type is obvious, or by naming a different function.
The implementation must cover every overload
You cannot overload two functions that take the same parameters and differ only in return type. The compiler decides from the arguments at the call site, and a return type is not an argument. This does not type-check:
Example — will not compile
function value(): number;
function value(): string;The implementation’s parameter list must accept every overload’s arguments. A common pattern is a union (number | string) or an optional parameter. If the body cannot actually handle a combination, the call signatures should not offer that combination.
Default arguments can clash with overloads. If greet() andgreet(name: string = "Ada") both exist as call shapes, a call with no arguments has two matches. Keep default arguments or overloads, not both for the same call shape.
Keep the meaning the same
Overload when the versions do the same kind of work. Do not reuse a name for unrelated jobs. A reader who seesadd should get a sum or a concatenation of the same idea, not a file write. Next: a function that calls itself.