Method overloading in Typescript

Method overloading within TypeScript feels slightly strange, especially if you’re coming from a language such as C#.

In essence we declare the overloads but only have a single implementation which should handle the alternate arguments from the declared overloads by checking the types passed in. Don’t worry, we’ll look at an example which will make this clear.

The first thing to know about TypeScript overloads is if we declare overloads with the same number of arguments in the methods, these will be transpiled to JavaScript and the static types of the arguments is lost.

In other words, if you have

class Options {
   static if<T>(value: T, condition: boolean): void {
   }
   static if<T>(value: T, numberParameter: number): void {
   }
}

the type information would be lost when transpiled, effectively meaning both methods would look like this

static if(value, condition);

So how do we do method overloading in TypeScript?

We can create method overloads with different numbers of arguments within TypeScript, but we “declare” the method signatures without implementations, for example

class Options {
   static if<T>(value: T, condition: boolean): void;
   static if<T>(value: T, numberParameter: number, stringParameter: string): void;

   // implementation to follow
}

As can be seen we have two overloads of the if method taking two and three arguments respectively, but these methods have no implementation. Instead we need to create another overload which can take all the arguments.

In this case we have a method with two arguments and one with three, hence our implementation needs to take three arguments. We then need to either match the argument types (if all overloads take the same type for any argument) or handle different types via a union or an any type and then check the type passed into the method using typeof or instanceof.

So for example, here’s an implementation of a method which can handle the types passed to either of the “declared” overrides

static if<T>(value: T, stringOrNumberParameter: any, stringParameter?: string): void {
   if (stringOrNumberParameter && typeof stringOrNumberParameter == "number") {
      // handle the second argument as a number
   }
   else {
      // handle the second argument as a string
   }
}

Notice how the third argument needs to be optional or have a default so that we can still use the two argument overload.

Hence, we end up with the following

class Options {
   static if<T>(value: T, condition: boolean): void;
   static if<T>(value: T, numberParameter: number, stringParameter: string): void;
    
   static if<T>(value: T, stringOrNumberParameter: any, stringParameter?: string): void {
      if (stringOrNumberParameter && typeof stringOrNumberParameter == "number") {
         // handle the second argument as a number
      }
      else {
         // handle the second argument as a string
      }
   }
}

We can implement overloads with the same number of arguments, within TypeScript. Again the types are lost once transpiled, but let’s take this example

function run(option: null): null;
function run(option: number): number;
function run(option: string): string;
function run(option: any): any {
  if(typeof option == "string") {
    // string implementation
  }
}

which again gets transpiled without types to

function run(option) {
}

however during development the types are respected. In such scenarios one might also use unions, such as

function run(option: string | number | null) {
}