DEV Community

Nikolas ⚡️
Nikolas ⚡️

Posted on • Updated on • Originally published at nikolasbarwicki.com

Mastering function overloading in Typescript

Introduction

Have you ever found yourself in a situation where you need a flexible function that can accept either a string or an array of strings as an argument and return either a string or an array of strings depending on the type of the argument?

Function overloading in TypeScript is a powerful feature that can help you solve this problem. In this article, we will explore how to use function overloading in TypeScript to write more expressive and flexible code, handle different combinations of parameter types and/or counts, and make your code more readable and maintainable.

How to use it?

To use function overloading, you can define multiple function signatures with the same name but different parameters. For example:

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
    return a + b;
}
Enter fullscreen mode Exit fullscreen mode

In this example, the add function is overloaded with two signatures: one that takes two numbers and returns a number, and another that takes two strings and returns a string. The implementation of the function (the last function definition) can handle any type of input and simply adds the two parameters together.

When the add function is called with a set of parameters, TypeScript will try to find the best match based on the parameter types, and call the corresponding implementation.

For example:

console.log(add(1, 2)); // 3
console.log(add("Hello, ", "world!")); // "Hello, world!"
Enter fullscreen mode Exit fullscreen mode

In the first call to add, TypeScript matches the first function signature (two numbers) and calls the implementation with the parameters 1 and 2, resulting in the output 3. In the second call, TypeScript matches the second function signature (two strings) and calls the implementation with the parameters "Hello, " and "world!", resulting in the output "Hello, world!".

More complex examples

In the first example, the concatenate function is overloaded with two signatures: one that takes two strings and returns a string, and another that takes three strings and returns a string.

// Overload with different number of parameters
function concatenate(a: string, b: string): string;
function concatenate(a: string, b: string, c: string): string;
function concatenate(a: any, b: any, c?: any): any {
    if (c) {
        return a + b + c;
    } else {
        return a + b;
    }
}
console.log(concatenate("hello", "world")); // "helloworld"
console.log(concatenate("hello", "world", "!")); // "helloworld!"
Enter fullscreen mode Exit fullscreen mode

In the second example, the divide function is overloaded with two signatures, one that takes two number and returns a number and another that takes two strings and returns a string.

// Overload with different parameter types
function divide(a: number, b: number): number;
function divide(a: string, b: string): string;
function divide(a: any, b: any): any {
    if (typeof a === "number" && typeof b === "number") {
        return a / b;
    } else {
        return a + " / " + b;
    }
}
console.log(divide(10, 2)); // 5
console.log(divide("10", "2")); // "10 / 2"
Enter fullscreen mode Exit fullscreen mode

And lastly the most complex example in this article.

It handles three different sets of parameters. You can easily say that this function is a wrong example because this function has too many resposibilites - and you're probably right. This example is meant to show how many posibilites using function overloading can give you when using Typescipt:

// Overload with different parameter types and rest parameters
interface Person {
    name: string;
    age: number;
}
function createPerson(name: string, age: number): Person;
function createPerson(options: { name: string, age: number }): Person;
function createPerson(options: { name: string, age: number }, ...hobbies: string[]): Person;
function createPerson(a: any, b?: any, ...c: any[]): any {
    if (typeof b === "number") {
        return { name: a, age: b }
    } else if (typeof b === "undefined" && typeof a === "object") {
        return { name: a.name, age: a.age }
    } else {
        return { name: a.name, age: a.age, hobbies: c }
    }
}
console.log(createPerson("John", 30)); // { name: "John", age: 30 }
console.log(createPerson({ name: "Jane", age: 25 })); // { name: "Jane", age: 25 }
console.log(createPerson({ name: "Bob", age: 35 }, "reading", "coding", "traveling")); // { name: "Bob", age: 35, hobbies: ["reading", "coding", "traveling"] }
Enter fullscreen mode Exit fullscreen mode

Defining and typing a function multiple times

You can also use a type or an interface to describe the different overloads of a function and avoid repeating the function signature and return type multiple times:

interface Add {
    (a: number, b: number): number;
    (a: string, b: string): string;
}
let add: Add = (a: any, b: any): any => {
    return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can use a type to describe the different overloads of a function:

type Add = {
    (a: number, b: number): number;
    (a: string, b: string): string;
}
let add: Add = (a: any, b: any): any => {
    return a + b;
}
console.log(add(1, 2)); // 3
console.log(add("Hello, ", "world!")); // "Hello, world!"
Enter fullscreen mode Exit fullscreen mode

By using a type or an interface to describe the different overloads of a function, you can avoid repeating the function signature and return type multiple times, and it makes the code more readable and maintainable.

Additionally, the TypeScript compiler will check that the implementation of the function is consistent with the overloads defined in the type or interface, ensuring that the function is always called with the correct parameter types and counts.

Passing wrong type or fewer arguments than expected

When you provide an argument of the wrong type or fewer arguments than expected to a function that is overloaded in TypeScript, the TypeScript compiler will try to find the best match among the available overloads based on the parameter types and counts. If it can't find a match, it will throw a compile-time error.

Providing an argument of the wrong type:

function concatenate(a: string, b: string): string;
function concatenate(a: number, b: number): number;
function concatenate(a: any, b: any): any {
    return a + b;
}
console.log(concatenate("hello", 5)); // compile-time error
Enter fullscreen mode Exit fullscreen mode

Here, in this example the function concatenate is overloaded with two signatures: one that takes two strings and returns a string, and another that takes two numbers and returns a number. The call to the function with the arguments "hello" and 5 will result in a compile-time error, because the TypeScript compiler can't find a match between the provided arguments and the available overloads.

Providing less arguments than expected:

function divide(a: number, b: number): number;
function divide(a: number, b: number, c: number): number;
function divide(a: any, b: any, c?: any): any {
    return a / b;
}
console.log(divide(10)); // compile-time error
Enter fullscreen mode Exit fullscreen mode

Here, in this example the function divide is overloaded with two signatures: one that takes two numbers and returns a number, and another that takes three numbers and returns a number. The call to the function with only one argument will result in a compile-time error, because the TypeScript compiler can't find a match between the provided arguments and the available overloads.

Providing more arguments than expected:

function multiply(a: number, b: number, c?: number): number;
function multiply(a: number, b: number): number;
function multiply(a: any, b: any, c?: any): any {
    return a * b * c;
}
console.log(multiply(2, 3, 4, 5)); // compile-time error
Enter fullscreen mode Exit fullscreen mode

Here, in this example the function multiply is overloaded with two signatures: one that takes two numbers and returns a number and another that takes three numbers and returns a number. The call to the function with four arguments will result in a compile-time error, because the TypeScript compiler can't find a match between the provided arguments and the available overloads.

In all these examples, the TypeScript compiler can't find a match between the provided arguments and the available overloads, and it will throw a compile-time error. It's important to keep in.

Conclusion

In summary, function overloading in TypeScript is a powerful feature that allows you to write more expressive and flexible code, group related functions under the same name and handle different combinations of parameter types and/or counts.

⚡️ Action Points

Top comments (0)