Function overloading in TypeScript allows you to have multiple functions with the same name but with a different signature (parameters and types). This means that the parameters have different types or the number of parameters is different for each function. The correct function to call is determined at runtime based on the arguments passed.
For example, consider a function that takes an argument and returns its square. If the argument is a number, you can calculate the square by multiplying the number by itself. However, if the argument is an array of numbers, you can calculate the square by squaring each element of the array.
function square(x: number): number;
function square(x: number[]): number[];
function square(x: number | number[]): number | number[] {
if (typeof x === 'number') {
return x * x;
} else {
return x.map(e => e * e);
}
}
square(3); // => 9
square([3, 5, 7]) // => [9, 25, 49]
Here the first two lines describe the function signature with two different argument types, number
and number[]
. The actual function implementation begins on the third line, and its signature must be a union of number | number[]
. The implementation uses the typeof
operator to determine which branch of the implementation to use.
Another example would be a database query function that reads a user from the database based on an ID (number), username (string), or attributes (object).
function query(id: number): User;
function query(username: string): User;
function query(attributes: Record<string, any>): User;
function query(arg: number | string | Record<string, any>): User {
let condition = '';
if (typeof arg === 'number') {
// Query the database using an id
condition = `id: ${arg}`;
} else if (typeof arg === 'string') {
// Query the database using a username
condition = `username: "${arg}"`;
} else {
// Query the database using attributes
condition = JSON.stringify(arg);
}
return db.query(`SELECT * FROM User WHERE ${condition}`);
}
query(1); // => id: 1
query('johndoe'); // => username: johndoe
query({ firstName: 'John', lastName: 'Doe' }); // => attributes: {"firstName":"John","lastName":"Doe"}
In this example, the query function is overloaded three times, each time with a different signature. The first signature accepts a single parameter of type number
, the second signature accepts a single parameter of type string
, and the third signature accepts a single parameter of type Record<string, any>
, which is a simple key-value map.
The examples I gave earlier show how you can use function overloading to process different types of input and give you an idea of how it can be useful in practice.
Top comments (9)
You can also use the
is
keyword to determine which type of parameters was provided:Yes, that's a good addition.
I'm looking at this and trying to figure out how this is useful in practice 😅
What's the benefit of defining the overloaded functions when the actual implementation has to include all possible types in each parameter slot anyway?
The main benefit is that you get the correct return type based on the parameter used.
When using
const a = square(2)
,a
is anumber
, and when usingconst a = square([1,2,3,4])
,a
is anumber[]
. Without the overloading, in both casesa
would benumber | number[]
.IMHO, the advantage of overloading a function is that there is a clear and precise function interface (signature) on the consuming side, i.e. the code that calls the function. It is much easier to understand what the function does if, for example, VSCode displays the three different options with the correct parameter name and type:
In plain JavaScript, you would have one parameter thats named like
idOrUserNameOrAttributes
or more arbitrarilyparam
and you have to figure what types are supported.Regarding your second question: you don't actually need to include all possible types in the implementation signature. The implementation can simply use
any
instead of the union type:Another great thing is that you can have a different number of parameters for each overloaded signature. For example, a date formatting function would have four signatures with a different number of parameters:
Again, an editor like VSCode will display the correct function signature depending on the parameters passed, e.g. if you pass three numbers, the function will display for month, day and year.
So ultimately there's the benefit of getting better argument hints; at the cost of extra noise/complexity in the code. Since there isn't support for true function overloading in JS I'd typically define each 'overloaded' function with a separate function name and, if need be, have a 'parent' function that determines which sub-function to call depending on argument types. It would be nice if there were a way to type the parent function based on the children 🤔
This didn't demonstrate a use case of parameter shifting.
Something likes
server.listen(...)
avoid function overloading if possible, because of the confusing type hint
most of the time union type is sufficient, else use conditional type with JSDoc and type level literal error message
Which type hint confuses you?