Polymorphic Typescript - Function overloading with rest parameters
Tags: typescript, tutorial, webdev, showdev
Published: 2020-05-12 09:17:36

---
title: Polymorphic Typescript - Function overloading with rest parameters
published: true
description: Using TypeScript to support multiple argument types including rest parameters
tags: typescript,tutorial,webdev,showdev
canonical_url: https://tane.dev/2020/05/polymorphic-typescript-function-overloading-with-rest-parameters/
---

Recently in [RxJS Primitives](https://tanepiper.github.io/rxjs-primitives/) I encountered a situation where one of the methods - [concat](https://tanepiper.github.io/rxjs-primitives/modules/string.html#concat) was initially designed to take an argument list of strings and in the method used rest (`...args`) parameters, mimicking the signature and passing them to the [String.prototype.concat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat)

> I've created a [StackBlitz Project](https://stackblitz.com/edit/function-overloading-with-rest) with the code for each
> step that can be followed along.

I wanted to refactor it to support an array of strings, but found that in the current implementation [this is not possible](https://stackblitz.com/edit/function-overloading-with-rest?file=concat-overload-1.ts) and throws a TypeScript
error:

```ts
export function concat(...args: string[]): MonoTypeOperatorFunction<string> {
  return (source: Observable<string>) => source.pipe(map(value => value.concat(...args)))
}

fromString('Testing').pipe(concat([' one', ' two'])).subscribe(console.log)
```

```
Argument of type 'string[]' is not assignable to parameter of type 'string'.
```

Due to how TypeScript treats rest parameters, it expects a list of parameters as a single string which is turned into an array-like [arguments](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments).

To get around this, we can use TypeScript [`function` overloading](https://www.typescriptlang.org/docs/handbook/functions.html#overloads).

## How to overload functions with rest parameters

My first attempt at writing this method lead to [this code](https://stackblitz.com/edit/function-overloading-with-rest?file=concat-overload-2.ts) which attempts to keep the type information in all implementations:

```ts
function concat(...args: string[]): MonoTypeOperatorFunction<string>;
function concat(args: string[]): MonoTypeOperatorFunction<string>;
function concat(...args: string[]): MonoTypeOperatorFunction<string> {
  return (source: Observable<string>) => source.pipe(map(value => value.concat(...args)))
}

export { concat }
```

This resulted in an error on the second implementation:

```
function concat(args: string[]): MonoTypeOperatorFunction<string> (+1 overload)
This overload signature is not compatible with its implementation signature.
```

It appears that TypeScript cannot convert an Array rest type to an `arguments` Array-like value, to get around this we need to use the `any` value in the [last implementation](https://stackblitz.com/edit/function-overloading-with-rest?file=concat-overload-3.ts):

```ts
function concat(...args: string[]): MonoTypeOperatorFunction<string>;
function concat(args: string[]): MonoTypeOperatorFunction<string>;
function concat(...args: any): MonoTypeOperatorFunction<string> {
  return (source: Observable<string>) => source.pipe(map(value => value.concat(...args)))
}

export { concat }
```

So now the TypeScript compiler stops complaining, and we can test it using both supported argument types:

```ts
fromString('Testing').pipe(concat(' one', ' two')).subscribe(console.log) // Testing one two
fromString('Testing').pipe(concat([' one', ' two'])).subscribe(console.log) // Testing one, two
```

However, if you look at the output of the result you'll notice a bug in our Array implementation where a comma appears in the text - the issue is that now the implementation treats the array as the first argument in a list of `arguments` - and changing the last method to be `args:any` would remove our rest parameter destructing.

To solve this, we need to use the `any` type again to destructure our arguments and check for the first one being an array, if it is then we use this as our destructured arguments into the `String.prototype.concat` method, but if it's a string then we pass all the arguments using destructuring:

```ts
function concat(...args: string[]): MonoTypeOperatorFunction<string>;
function concat(args: string[]): MonoTypeOperatorFunction<string>;
function concat(...args: any): MonoTypeOperatorFunction<string> {
  const inArgs: any[] = [...args];
  if (inArgs[0] instanceof Array) {
    return (source: Observable<string>) => source.pipe(map(value => value.concat(...inArgs[0])))
  }
  return (source: Observable<string>) => source.pipe(map(value => value.concat(...inArgs)))
}

export { concat }
```

Now [the implementation](https://stackblitz.com/edit/function-overloading-with-rest?file=concat-overload-4.ts) works as intended - our method can accept one or more string arguments, or an array of strings as the first argument - and it's not possible to mix the two up - if you try add a second array to the arguments, the TypeScript compiler will look for single string arguments.