I spent a couple of hours recently trying to solve a problem with overloading in TypeScript only to realise there wasn’t actually a problem at all – I had just completely misunderstood how overloading works !

Unlike other languages where an overload acts as an “alias” for the implementing method, in TypeScript an overload “replaces” / hides the implementing method – the implementing method becomes inaccessible unless called via an overload signature.

### Example

I’ll try to show a basic example and then go into more details.

Imagine, for the sake of this example, an equivalent of “parseInt” (that we could call doParseInt) that takes either a number or a string.

- If a number is passed we return the number directly.
- If a string is passed however, a second argument “radix” is required.

We could code this like:

// Basic function to test overloading: can be called in two ways // value is number (radix not used) // value is string (radix required) function doParseInt(value: (number | string), radix?: number): number { if (typeof value === 'number') { return value; } if (typeof value === 'string') { return parseInt(value, radix); } throw new Error(`unexpected value: ${value}`); }

We can use it like:

const val1 = doParseInt(123); // OK const val2 = doParseInt('123'); // Error - radix is required if value is string const val3 = doParseInt(123, 8); // Error - radix is not used if value is number const val4 = doParseInt('123', 10); // OK const val5 = doParseInt(123 as (number | string)); // OK

This works but is not great from a type-safety point of view – we have no way of forcing to people to pass a radix if value is string, or likewise from not passing a radix if value is numeric.

Overloading to the rescue – We add our overload signatures to refine the way we can call the implementing function:

function doParseInt(value: number): number; function doParseInt(value: string, radix: number): number;

**Note these are not “functions” – they do not have a body!**

They serve purely as “overload signatures” – method signatures that refine (limit) how we can call the implementing method.

After adding our signatures, everything seems great except “val5” which no longer compiles – it appears the fact of adding an overload signature has made the original implementing method inaccessible !

const val1 = doParseInt(123); // OK // const val2 = doParseInt('123'); // No longer compiles - great // const val3 = doParseInt(123, 8); // Likewise, great const val4 = doParseInt('123', 10); // OK const val5 = doParseInt(123 as (number | string)); // No longer compiles either .... !

Results in:

error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'.

The problem comes from val5 – we want to be able to call directly the non-overloaded implementation method.

However, this is not possible in TypeScript – adding a single overload makes the non-overloaded implementation inaccessible.

**To resolve this, we need to simply add the original method signature as an overload signature of itself.**

The final code looks like:

function doParseInt(value: number): number; function doParseInt(value: string, radix: number): number; function doParseInt(value: (number | string), radix?: number): number; // Basic function to test overloading: can be called in two ways // value is number (radix not used) // value is string (radix required) function doParseInt(value: (number | string), radix?: number): number { if (typeof value === 'number') { return value; } if (typeof value === 'string') { return parseInt(value, radix); } throw new Error(`unexpected value: ${value}`); }

In hindsight it seems clearly logical – we add overload signatures to refine/specialise the call, and so it doesn’t make much sense to leave the original implementing method available.

**What would have been great would have been an error message more explicit: **“*Implementing function not available if overload signature exists*” for example

This example is slightly contrived: The actual problem was with array signatures (RxJS combineLatest for example) but that will be the subject of my next post.