Mastering function overloading in Typescript
Tags: typescript, javascript
Published: 2023-01-22 09:00:00

## 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:

```typescript
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
    return a + b;
}
```

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:

```typescript
console.log(add(1, 2)); // 3
console.log(add("Hello, ", "world!")); // "Hello, world!"
```

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.

```typescript
// 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!"
```

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.

```typescript
// 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"
```
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:

```typescript
// 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"] }
```

## 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:
```typescript
interface Add {
    (a: number, b: number): number;
    (a: string, b: string): string;
}
let add: Add = (a: any, b: any): any => {
    return a + b;
}
```
Alternatively, you can use a type to describe the different overloads of a function:
```typescript
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!"
```
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:
```typescript
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
```
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:
```typescript
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
```
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:
```typescript
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
```
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
- [see my blog a about Javascript and React](https://www.nikolasbarwicki.com/)
- give a like or write a comment