Monday, November 7, 2022

Overloaded Methods in TypeScript

If you've ever worked with a language like C# or Java you're probably often using overloaded method signatures to provide callers with an opportunity to have a similar outcome by passing a different number or type of parameters. However in TypeScript which is just a superset of JavaScript and adhering to all things JS under the covers, method overloading doesn't work the same with identical method names and parameter signature because there are no types to differentiate between. If you have (2) identical methods in JS being called the latter defined method on the prototype will be called, and the former ignored.


TypeScript has the benefit of type definitions at build time so method overloading is possible... kind of. If the goal is to have intellisense to see multiple definitions of the same overloaded method, we can certainly achieve that. If the goal is to have multiple definitions of the same method name with different parameter signatures and separate, different implementations, this out of the box is not possible and won't be like traditionally static typed, structured languages like C#. Regardless let's see how overloading does work in the vanilla form (I'll hint at conditional types at the end) using TypeScript and you can decide if you can leverage for your benefit.

The recipe for overloaded methods in TypeScript is that you can create 1...n method signatures, but only have a single implemented method representing any of the possible call combinations. Let's look at a code sample:

Above we have the overloaded method named start, that represents the potential to provide the different procedures to start an engine, based on the various engine types. Note the (5) overloaded method signatures, but only a single implemented method. The reason for this is the overloaded behavior and differentiation of methods is a design/build time only feature available due to the fact we are using TypeScript. In fact if you look at the transpiled JavaScript the only method shown is the single implemented method:

If you do try and use implementations on more than 1 method with the same name, TypeScript will warn you with a, "Duplicate function implementation" warning.

Looping back to our original goal using the properly implemented code, as the method caller if we want to see at design time a list of the various signatures, we have indeed accomplished that goal as you may scroll through and see the multiple, overloaded definitions:

However this comes at a bit of a sloppy cost for that single implemented method. In order to make this work the method signature must encapsulate all potential values that could be sent to satisfy the TypeScript compiler. This usually equates to using a Union type in the method signature to account for all possible types. The next hurdle is because overloaded methods are really a fa├žade, you must manually pick apart what's sent and reverse engineer what you received at runtime. This usually equates to type guards, if statement, switch statements, or some combination to sniff out what you received, so you can proceed forward. All of this logic is that code above within our implemented method to determine what exactly we received.

It's even trickier to determine what's sent if you have (2) identical method signatures that are only differentiated by variable name like our 1st two methods below. This is not advisable even though it does work:

The long and the short of method overloading in TypeScript is that it is possible, but with a few caveats that may not make it sensible. I think if you only have (2) different method signatures, that are easily discernable at runtime in the implemented code, then this might make sense. However as the signature list expands, the logic to differentiate the potential values sent can get unwieldly. 

Lastly another potential option may be to use conditional types in the method signatures which rely on generics to sort out the types based on what the caller is sending. This could reduce the need for the implemented method to contain all the logic to sort out which values it was sent as it will be know already. However in this post I wanted to strictly do a 1:1 look at the concept of overloading as it may be known from other languages, and how it can be accomplished in TypeScript.