The secret trap when using ANY in TypeScript
Tags: web, typescript
Published: 2020-12-18 21:42:49

---
title: The secret trap when using ANY in TypeScript
published: true
date: 2020-10-13 00:00:00 UTC
tags: Web,TypeScript
canonical_url: https://tigeroakes.com/posts/any-wreaks-havoc-on-overloads/
cover_image: https://tigeroakes.com/posts/any-wreaks-havoc-on-overloads/banner.jpg
---

## What is `any`?

If you’re working with TypeScript, chances are you’ll work with [the `any` type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any). `any` essentially turns off typechecking, and allows the corresponding variable to be used for anything. You can call any methods on an `any` variable, and they’ll all return `any` as well. It’s great when you can’t write types for everything in your codebase.

```tsx
let obj: any = { x: 0 };
// None of these lines of code are errors
const foo: any = obj.foo();
obj();
obj.bar = 100;
```

## What are function overloads?

TypeScript has another [neat feature called function overloads](https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads). Some JavaScript functions return different results based on the arguments you supply, and this can be represented in TypeScript by written multiple function types on top of each other. Only one function signature can match at a time. The matching overload is determined by the arguments you supply to he function. [The first applicable overload will always be chosen](https://github.com/microsoft/TypeScript/issues/1860#issuecomment-72154737).

```tsx
function convertType(value: string): number;
function convertType(value: number): string;
function convertType(value: string | number): number | string {
  if (typeof value === 'number') {
    return value.toString();
  } else {
    return parseFloat(value);
  }
}

const num: number = convertType('number');
const str: string = convertType(num);
```

## Using overloading with arrays

Some functions want to transform an array in some way, and return an array with the same length and slightly modified types. [A good example of this is `Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), which transforms an array of promises into a single promise that resolves with an array of values.

Using [generic function definitions](https://www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions), you can infer the type of the array passed into `Promise.all`. However, the resulting type will be an array without positional data.

```tsx
class Promise {
  static all<T>(array: (T | Promise<T>)[]): T[];
}

Promise.all([Promise.resolve(10), Promise.resolve('hello world')]).then(
  (result) => {
    // result's type is (number | string)[]
    // @ts-expect-error: Type 'string' is not assignable to type 'number'.
    const num: number = result[0];
  }
);
```

TypeScript does let you infer type of specific array items, but then you need to hardcode the length. Using overloading, you can have a couple of variations for different array lengths.

```tsx
class Promise {
  static all<A, B, C>(
    array: [A | Promise<A>, B | Promise<B>, C | Promise<C>]
  ): [A, B, C];
  static all<A, B>(array: [A | Promise<A>, B | Promise<B>]): [A, B];
  static all<A>(array: [A | Promise<A>]): [A];
  // fallback to unknown length
  static all<T>(array: (T | Promise<T>)[]): T[];
}

Promise.all([Promise.resolve(10), Promise.resolve('hello world')]).then(
  (result) => {
    // result's type is [number, string]
    // This line is no longer an error
    const num: number = result[0];
  }
);
```

However, you can only write so many overloads. Eventually you need to fallback to an array with an unknown length like above. TypeScript’s [official type definitions for `Promise.all`](https://github.com/microsoft/TypeScript/blob/065a996345fcfafd3c744d2a724a1ae9f31f9ab0/lib/lib.es2015.promise.d.ts#L41) hardcodes arrays up to length 10, and fallback after that.

## How `any` creates problems with overloads

Remember that `any` will match with any type, and that function overloads use the first applicable overload. These two facts cause problems when you pass a variable with type `any` into a function with overloads.

```tsx
function convertType(value: string): number;
function convertType(value: number): string;
function convertType(value: string | number): number | string {
  if (typeof value === 'number') {
    return value.toString();
  } else {
    return parseFloat(value);
  }
}

const num: any = 10;
// @ts-expect-error: Type 'number' is not assignable to type 'string'.
const str: string = convertType(num);
```

The first overload is always used when you pass in a variable with type `any`, because it will be applicable to that signature. Even if there is a more generic signature later, the first overload is chosen. The overload ordering cannot be reversed, because the more generic signature will match every variable you pass in. As far as I know, you can’t write a signature that _only_ matches if the variable is type `any`, because matching with anything works in both directions.

In the case of `Promise.all`, the first function overload signature is an array with a hardcoded length of 10. That can create confusing bugs like this, where `any` is passed in and the resulting type becomes an array of exactly 10 `unknown`s.

{% tweet 1315782177288679424 %}

## Future solutions

Hardcoding the array length isn’t great, because you can only support so many variations. TypeScript 4.0 introduces a new feature called [“variadic tuple types”](https://devblogs.microsoft.com/typescript/announcing-typescript-4-0/#variadic-tuple-types), which lets you capture the exact array argument and transform it. [Future type definitions for `Promise.all`](https://github.com/microsoft/TypeScript/pull/39796) may replace all the function overloads with a single function signature, removing the `any` bug entirely.

TypeScript may also add special handling for passing `any` into function overloads in the future. If you know of an existing issue, or see something I missed, let me know.