Function Overloading in TypeScript

Function Overloading in TypeScript

Function overloading in TypeScript is the ability of a function to be defined with multiple signatures, each defining the number and types of parameters and the return type.

When a function is called, the TypeScript compiler will check each of the defined signatures to find the most specific signature that matches the arguments supplied at the call site.

For example, consider a function called calculateArea that calculates the area of a shape. You can overload this function to accept different types of shapes as arguments, like a rectangle, a circle or a triangle:

function calculateArea(shape: string, width: number, height: number): number;
function calculateArea(shape: string, radius: number): number;
function calculateArea(shape: string, side1: number, side2: number, side3: number): number;

function calculateArea(shape: string, arg1: number, arg2?: number, arg3?: number): number {
  switch (shape) {
    case 'rectangle':
      return arg1 * arg2;
    case 'circle':
      return Math.PI * arg1 ** 2;
    case 'triangle':
      const semiPerimeter = (arg1 + arg2 + arg3) / 2;
      return Math.sqrt(semiPerimeter * (semiPerimeter - arg1) * (semiPerimeter - arg2) * (semiPerimeter - arg3));
    default:
      throw new Error(`Unsupported shape: ${shape}`);
  }
}

In this example, the calculateArea function is overloaded with three different signatures, each accepting a different combination of arguments based on the shape of the object.

Using the above example, you can call the calculateArea function with different arguments to obtain the area of different geometrical shapes:

console.log(calculateArea('rectangle', 7, 11)); // 77
console.log(calculateArea('circle', 6)); // 113.09733552923255
console.log(calculateArea('triangle', 3, 4, 5)); // 6

Method overloading

Method overloading in TypeScript allows you to define multiple methods with the same name but different parameter types and return types.

Here's an example that shows method overloading in TypeScript:

class Helper {
  reverse(str: string): string;
  reverse(arr: number[]): number[];
  reverse(x: string | number[]): string | number[] {
    if (typeof x === 'string') {
      return x.split('').reverse().join('');
    } else if (Array.isArray(x)) {
      return x.reverse();
    }
  }
}

const helper = new Helper();

console.log(helper.reverse('foo')); // Output: oof
console.log(helper.reverse([11, 12, 13, 14, 17])); // Output: [17, 14, 13, 12, 11]
console.log(helper.reverse(true)); // TypeScript error: Argument of type 'true' is not assignable to parameter of type 'string | number[]'.

In this example, we have a Helper class with a reverse() method that has two signatures, one that takes a string argument and another that takes an array of numbers. The third implementation of the reverse() method accepts a parameter of type string or number array and it returns either reversed string or reversed array depending on the input param type.

When calling the reverse() method with a string argument, TypeScript will call the first signature of the method, which returns a reversed string. When calling it with an array of numbers, it will call the second signature of the method, which returns a reversed array.

If reverse() method is called with an argument of a type other than string or number array, TypeScript will throw a compilation error, saying "Argument of type 'true' is not assignable to parameter of type 'string | number[]'".

Constructor overloading

Constructor overloading is a feature that allows a class to have multiple constructor definitions with different parameters. This helps in providing flexibility while creating an object of a class.

The syntax for constructor overloading in TypeScript is:

class MyClass {
  constructor(param1: type1);
  constructor(param1: type1, param2: type2);
  constructor(param1: type1, param2?: type2) {
    // implementation
  }
}

In the above example, the MyClass class has two constructor definitions. The first constructor takes one parameter of type type1 and the second constructor takes two parameters, one of type type1 and the other type type2. The third constructor is the implementation which can have any number of parameters.

When an object of the MyClass class is created, TypeScript will choose the constructor with the matching number and types of arguments.

Here is an example of constructor overloading:

class Account {
  constructor(public accountNumber: string);
  constructor(public accountNumber: string, public accountType: string);
  constructor(public accountNumber: string, public accountType?: string, public balance?: number) {
    if (balance === undefined) {
      this.balance = 0;
    } else {
      this.balance = balance;
    }
  }

  deposit(amount: number) {
    this.balance += amount;
  }

  withdraw(amount: number) {
    if (amount > this.balance) {
      console.log('Insufficient funds');
    } else {
      this.balance -= amount;
    }
  }

  printBalance() {
    console.log(`Account balance: $${this.balance.toFixed(2)}`);
  }
}

// Create an account with just the account number
const account1 = new Account('123456789');

// Create an account with the account number and account type
const account2 = new Account('987654321', 'savings');

// Create an account with the account number, account type, and initial balance
const account3 = new Account('135792468', 'checking', 750);

console.log(account1);
console.log(account2);
console.log(account3);

// Deposit and withdraw from accounts
account2.deposit(1537);
account2.printBalance();

account3.withdraw(341);
account3.printBalance();

In the above example, the Account class has three constructor definitions. The first constructor takes only the accountNumber parameter, the second constructor takes both accountNumber and accountType parameters, and the third constructor takes all three parameters accountNumber, accountType and balance.

The deposit(), withdraw(), and printBalance() methods allow you to perform transactions on the account.

When creating account1, TypeScript chooses the first constructor of the Account class, which takes only the accountNumber parameter. When creating account2, TypeScript chooses the second constructor of the Account class, which takes both accountNumber and accountType parameters. When creating account3, TypeScript chooses the third constructor of the Account class, which takes all three parameters accountNumber, accountType and balance.

Conclusion

The main benefit of function overloading is the ability to define different functions with the same name but different sets of parameters. This leads to more efficient code reusability, readability, and maintainability of code.