Quick guide to Function Overloading in TypeScript
Tags: typescript, beginners, tutorial, webdev
Published: 2022-06-16 13:45:57

We use functions all the time in our apps, generally speaking most of the JS code are functions. TypeScript gives us many ways to describe them.
For a basic function, this is fairly easy to do, but what if the function takes a variety of argument counts and types, or even returns a different type depending on how it's called?
For such cases, TypeScript has the handy [function overloading](https://en.wikipedia.org/wiki/Function_overloading) feature. Let's see how to use it and how it can help you improve your code.

## Function typing
I feel hungry now, so let's create a function that will cook a dish:

```typescript
function cookDish(ingredient: string): string {
  return `${ingredient}🍴 is ready, bon appétit!`;
}
```
We passed a single `string` argument as an ingredient and it returns a `string` dish after invocation:
```javascript
cookDish('🥚'); // '🍳🍴 is ready, bon appétit!'
```

But wait, this is not enough, I definitely need a sandwich here!
To cook it, we need to modify our function, so that it could accept multiple ingredients:

```typescript
function cookDish(ingredient: string | string[]): string {
  const tableSet = '🍴 is ready, bon appétit!';
  if (typeof ingredient === 'string') {
    return `${ingredient}${tableSet}`;
  } else if (Array.isArray(ingredient)) {
    return `${ingredient.join('')}${tableSet}`
  }
  throw new Error('Nothing to cook 😭');
}
```
We used `Union` type to describe our function argument, that can be either one `string` or multiple `string[]` of them:
```javascript
cookDish('🌽'); // '🍿🍴 is ready, bon appétit!';
cookDish(['🍞', '🍅', '🥓']); // '🥪🍴 is ready, bon appétit!';
```

Adding types to support various arguments is a common and good approach in most cases, but sometimes you need to explicitly define all the ways to call a function. This is where **function overloading** comes into play.

## Function overloading
In the case of a rather complex function, to improve usability and readability, it is always better to use the function overloading feature to improve usability and readability. This approach is considered the most flexible and transparent.

To use this, we need to write some function signatures:

1. _Overload_ signature - defines different ways to call a function: arguments and return types, and doesn't have a body. There can be multiple overload signatures (usually two or more).
2. _Implementation_ signature - provides an implementation for a function: function body. There can be only one implementation signature and it must be compatible to overload signatures.

Let's rewrite our `cookDish` using function overloading:
```typescript
// Overload signature
function cookDish(ingredient: string): string
function cookDish(ingredients: string[]): string

// Implementation signature
function cookDish(ingredient: any): string {
  const tableSet = '🍴 is ready, bon appétit!';
  if (typeof ingredient === 'string') {
    return `${ingredient}${tableSet}`;
  } else if (Array.isArray(ingredient)) {
    return `${ingredient.join('')}${tableSet}`
  }
  throw new Error('Nothing to cook 😭');
}
```

Two overload signatures describe two different ways the function can be called: with `string` or `string[]` argument. In both ways we will get a cooked `string` dish as a result.
An implementation signature defines the behavior of a function within a function body.
Our function invocation doesn't change though, we can cook as before:
```javascript
cookDish('🍚'); // '🍙🍴 is ready, bon appétit!';
cookDish(['🫓', '🍅', '🥑']); // '🥙🍴 is ready, bon appétit!';

const money: any = 100;
cookDish(money);
/**
⛔ No overload matches this call.
Overload 1 of 2, '(ingredient: string): string', gave the following error.
  Argument of type 'any' is not assignable to parameter of type 'string'.
Overload 2 of 2, '(ingredients: string[]): string', gave the following error.
  Argument of type 'any' is not assignable to parameter of type 'string[]'.
*/
```
Note at the last example above: ~~money can't cook you a dish~~ although an implementation signature is the one that implements our function, it cannot be called directly, even though it accepts `any` as an argument `function cookDish(ingredient: any)`. You can call only overload signatures.

It's dinner time! let's look at a slightly more complex example:
```typescript
function cookDinner(dish: string): string
function cookDinner(money: number): string // Wrong argument type
/**
⛔ This overload signature is not compatible with its implementation signature.
*/
function cookDinner(dish: string, drink: string, dessert: string): string
function cookDinner(dish: string, drink?: string, dessert?: string): string {
  let dinner = `${dish}🍴`
  if (drink && dessert) {
    dinner += ` ${drink}🧊${dessert}🥄`
  }
  return `${dinner} is ready, bon appétit!`
}

cookDinner('🍝'); // '🍝🍴 is ready, bon appétit!';
cookDinner('🍔', '🥤', '🍰'); // '🍔🍴🥤🧊🍰🥄 is ready, bon appétit!';

cookDinner('🍺', '🍸') // 🤒 Invalid number of arguments
/**
⛔ No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
*/
```
Need to remember that _implementation signature must be compatible to overloads and can't be called directly_.

## Conclusion
Function overloading is a powerful TypeScript feature that allows you to type your functions more elegantly.

**More read:**

- [Function Overloads](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads)
- [Function overloading Do's and Don'ts](https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#function-overloads)
- [Arrow functions overload](https://stackoverflow.com/a/53143568)

Hope you enjoyed this guide, stay tuned for more.