Typescript overload function and constructor in a class

Every object-oriented language provides OOP concepts such as Inheritance, Polymorphism, and Overloading.

TypeScript, like many others, includes overloading methods and constructors with the same name, wherein overloading is defined as using the same name with different parameters and return types.

TypeScript offers a distinct approach compared to many other programming languages. While it does provide overloading, it implements it differently. So, what exactly is overloading in TypeScript?

Typically, in any programming language, we might attempt to write code like this.

class OverloadDemo {
  constructor() {}
  method(num: number) {
    console.log("method with a number argument");
  }
  method(message: string) {
    console.log("method with a string argument");
  }
}
let demo = new OverloadDemo();
console.log(demo.method(12));

However, this code doesn’t work as expected. Instead, it gives a compilation error and throws a Duplicate function implementation error. This occurs because during compilation into JavaScript, both function definitions appear the same, and JavaScript doesn’t understand overloading.

To address the Duplicate function implementation error, we need to provide a single function that can accept variable arguments.

It’s important to note that overloading applies to both functions and constructors in TypeScript.

Typescript overload methods

Function overloading involves providing the same function name with different arguments, offering a flexible implementation where arguments may vary.

In any overloaded function, there are different things to be noted

  • The argument count might differ.
  • The argument type may vary.
  • The return type can be variable.

How do overloads work in TypeScript?

TypeScript allows programmers to define a single function that behaves differently based on the number and types of arguments provided.

Achieving this flexibility comes with certain limitations, but there are several approaches to tackle it.

  • Function overload with Any data type

    The Any data type in TypeScript is a predefined type that allows a variable to be assigned any value, whether it’s a string or a number.

    Below, we rewrite the previous code with the following considerations:

    We provide a single function with a parameter of type any, allowing us to pass either a string or a number. Despite having different arguments, this method demonstrates overload functionality while maintaining the same argument count.

    class OverloadAnyDemo {
      constructor() {}
      method(num: any) {
        console.log("method ", typeof num);
      }
    }
    let demo = new OverloadAnyDemo();
    
    console.log(demo.method(12)); // returns 'number'
    console.log(demo.method({})); // returns 'object'
    console.log(demo.method(true)); // returns 'boolean'
  • Function Overload Using Union Type in TypeScript

    We extend our implementation with an additional function that adds numbers. This function can accept two or more numbers.

    TypeScript offers a Union Type, which combines multiple types under a single data type.

    class OverloadAnyDemo {
      constructor() {}
      method(message: string | any) {
        console.log("method ", typeof message);
      }
    }
    let demo = new OverloadAnyDemo();
    console.log(demo.method(12)); // returns 'number'
    console.log(demo.method({})); // returns 'object'
    console.log(demo.method(true)); // returns 'boolean'
  • Function Overload Using Optional Parameters in TypeScript

    We utilize optional parameters (symbolized by a question mark - ) in function arguments.

    method(message?: string | any) {
            console.log('method ', typeof message);
    }

    In this function, the string argument is optional, allowing the function to accept zero or one string argument. This serves as another method to achieve overloading.

Constructor Overloading in TypeScript

Constructors are created using the constructor keyword in TypeScript, resembling functions with some differences.

Let’s define a Circle class in TypeScript.

class Circle {
  radius: number;

  // Constructor signature with declared properties
  constructor(radius: number = 0) {
    this.radius = radius;
  }
}

Now, let’s add an overloaded constructor.

class Circle {
  // Constructor Overloads
  constructor(radius: number, color: string);
  constructor(color: string);
  constructor(radius: any, color?: any) {

  }
}

Similar to function overloading, we can have only one constructor with different arguments. This is a valid overload as it functions with the new operator.

Difference between Function Overload and Constructor Overload

  • Constructors do not allow typed parameters.
  • Type annotations are not permitted in constructor functions; they always return a class instance.
  • TypeScript only permits a single constructor implementation.

We can achieve constructor overloading using optional parameters, the Any data type, or Union Types.

TypeScript Overload with Arrow Functions

Let’s apply constructor overloading for a Circle.

class Circle {
  // Constructor Overloads
  constructor(radius: number);
  constructor(color: string);

}

You can rewrite the above constructor overloading with arrow functions as follows.

In this example, a type alias is created for the above overload using the type keyword.

You can rewrite the above constructor overloading with the arrow function as overload with the type keyword

type Circle = {
  (name: number): void,
  (name: string): void,
};
const circle: Circle = (name: number | string): void => {};

And the caller code is as follows.

circle(5);
circle("red");

Interface Overload in TypeScript

Let’s declare an interface with two overloaded methods having the same name but different arguments.

interface Shape {
  area(length: number, breadth: number): number;
  area(side: number): number;
}

Suppose you are declaring Rectangle and Square classes. Both Rectangle and Square should implement the Shape interface, necessitating the implementation of all interface methods as seen below.

class Rectangle implements Shape {
  area(length: number, breadth: number): number {
    // todo
  }
  area(side: number): number {
    // todo
  }
}

class Square implements Shape {
  area(length: number, breadth: number): number {
    // todo
  }
  area(side: number): number {
    // todo
  }
}

Since these shapes don’t require area calculations to be implemented twice, it’s not ideal to write an interface with overloading as shown above.

Let’s rewrite the overloaded interface methods:

interface Shape {
  area: ((length: number, breadth: number) => number) | ((side: number) => number);
}

And implementations for Rectangle and Square are as follows.

class Rectangle {
  area(length: number, breadth: number): number {
    // todo
  }
}

class Square {
  area(side: number): number {
    // todo
  }
}

Conclusion

You learned about TypeScript overloads using any, optional, and union types for:

  • Function overloading
  • Constructor overloading
  • Arrow functions for overloading
  • TypeScript interface method overloading