Function overloading (or Method Overloading) is a feature where two or more functions can have the same name but with different parameters and implementations. The function overloading is not available in JavaScript. But Typescript does allow us to create several overload signatures of a function. We can use those overload signatures to call the function and thus mimicking the function overloading. Let us learn how to create an overloaded function in TypeScript using examples
Table of Contents
What is Function Overloading?
Take a look at the following two Add functions. We either pass two numbers or an array of numbers. it will add the numbers are return the sum.
This add function accepts two numbers and returns their sum.
1 2 3 4 5 6 | function add(a:number, b:number):number { return a+b; } console.log(add(10,10)) //20 |
This add function accepts an array of numbers. It calculates their sum and returns it.
1 2 3 4 5 6 7 8 9 10 | function add(numArray:number[]):number { var sum = numArray.reduce(function(a, b){ return a + b; }, 0); return sum; } console.log(add([1,2,3,4,5,5])) //20 |
But, we cannot use both the functions either in JavaScript or in TypeScript.
JavaScript does allow us to create multiple functions with the same name. But it overrides the previously written function. It always calls the last declared function irrespective of how many functions you have declared.
While Typescript does not allow us to same name for multiple functions. It throws a Duplicate function implementation error if we try to do so.
The languages like C#, Java, etc. allows us to create functions with the same name, but with different parameters and return types. It is known as function overloading.
The ability to use two or more functions with the same name brings consistency in the naming of functions. It also makes it easier to remember the function names. For example, add
function always adds the numbers or joins the strings. A print
method will print whatever you pass into it.
But in JavaScript where we do not have overloading, we have two options.
One is to create separate functions for each variation in arguments like addNum
, addNumArray
etc. In this case, we need to check the data type of the arguments and call the correct function.
The second option is to create a single function and check the data type of the arguments passed and call the appropriate code within that function. This is the preferred way in JavaScript.
Creating overloaded functions in TypeScript
Since Typescript is based on JavaScript, it does not allow us to create Overloaded functions with the same name. Instead, it allows us to create overload signatures for a regular function.
To create function overloading first, we need to create a regular function. This function must check the type of the arguments passed to it and call the appropriate code within that function. We call this an implementation function.
Next, we create overload signatures for that function and use those signatures to call the function.
Let us learn how to do it using the add
function from the previous section
Overload Signatures
Overload signature defines how we intend to call the function. It contains only the signatures and does not contain any code. It is basically a function declaration without any code.
We would like to call our add function in two ways. One is to pass two numbers and the other way is to pass an array of numbers.
1 2 3 4 5 | add(10,10) //Pass two numbers to the add function add([1,2,3,4,5,6]) //Pass array of numbers to the add function |
Hence create a function declaration statement for the above, without the function body. This is our overload signatures.
1 2 3 4 | function add(num1:number,num2:number):number function add(numArray:number[]):number |
Implementation Function
The implementation function is a function that actually contains the code. Its signature must be generic enough to cover all the overloads.
Since our add function takes 2 arguments at most, our implementation function must also accept two arguments. We make the second argument optional because the first overload signature takes only one argument.
The first argument is number
in the first Overload signature and number[]
array in the second overload signature. Hence the first argument in the implementation function is a union type of number|number[]
.
Similarly, the second argument is the add function is of type number.
Inside the function, we check if the arg1
is an array and if yes we add the numbers. If the arg1
is not an array, then we make checks to see if arg1
& arg2
are numbers and add them and return the result back.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | function add(arg1:number|number[], arg2?:number):number { if (Array.isArray(arg1)) { var sum = arg1.reduce(function(a, b){ return a + b; }, 0); return sum; } if (typeof arg1=="number" && typeof arg2=="number") { //number return arg1 + arg2; } return 0; //default is zero. } |
Finally, we place the overload signatures directly above the implementation function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function add(num1:number,num2:number):number function add(numArray:number[]):number function add(arg1:number|number[], arg2?:number):number { if (Array.isArray(arg1)) { var sum = arg1.reduce(function(a, b){ return a + b; }, 0); return sum; } if (typeof arg1=="number" && typeof arg2=="number") { //number return arg1 + arg2; } return 0; } |
Now, you can call the function using the overload signatures.
1 2 3 4 | add(10,10) //20 add([1,2,3,4,5,6]) //21 |
Trying to call the function with any other way will result in a compiler error
1 2 3 4 5 6 7 8 9 | //Calling with a array. add(['a','b',1,2]) //No overload matches this call add(1,2,3) //Expected 1-2 arguments, but got 3. //String and a number add("1",2) //No overload matches this call. |
The number of Arguments
The overload signatures can have any number of arguments. But the number of arguments in the implementation function must be equal (or greater) to the overload signature with the most number of arguments.
In this example, one of the overload signatures has three arguments and the other one has one. Hence the implementation function must have at least three arguments.
1 2 3 4 5 6 7 8 | function odFunc(a:string):string function odFunc(a:number,b:string, c:string):string function odFunc(a:any,b?:any,c?:any):string { return "" } |
Optional Arguments
We need to mark some arguments as optional if any of the overload signatures do not have the same number of arguments as in the implementation function.
In this example, the first overload signature has only one argument. Hence in the implementation function, any argument above one must be marked as optional.
1 2 3 4 5 6 7 8 9 | function odFunc(a:string):string function odFunc(a:number,b:string, c:string):string //b & c is optional function odFunc(a:any,b?:any,c?:any):string { return "" } |
If we do not mark the argument as optional, then the first overload signature which has only one argument will not be compatible with the implementation function which has three arguments. It will result in “This overload signature is not compatible with its implementation signature” error.
1 2 3 4 5 6 7 8 9 10 | function odFunc(a:string):string function odFunc(a:number,b?:string,c?:string):string function odFunc(a:any,b:any,c:any):string { return a+b+c; } //This overload signature is not compatible with its implementation signature. |
The arguments data type must be compatible
The data type arguments in the implementation function must be compatible with the data type in the overload signature.
In this example, the data type of the first argument in overload signature is string and number. Hence the first argument in the implementation function must be a type that is compatible with both string
and number
. That gives us three options. A union type of string|number
, any
type & unknown type.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function odFunc(a:string,b:string):string function odFunc(a:number,b:string):string function odFunc(a:string|number,b:string):string { return a+b; } //This is also ok //function odFunc(a:any,b:string):string { // return a+b; //} //Even this works //function odFunc(a:unknown,b:string):string { // return a+b; //} |
Using an incompatible type will result in “This overload signature is not compatible with its implementation signature” compiler error.
1 2 3 4 5 6 7 8 9 10 | function odFunc(a:string,b:string):string function odFunc(a:number,b:string):string function odFunc(a:string,b:string):string { return a+b; } //This overload signature is not compatible with its implementation signature. |
The implementation function should be the last
The overload signatures must be placed directly above the implementation function. The following code is invalid, because of the let statement placed between the overload signature and the implementation function. Typescript compiler throws the “Function implementation is missing or not immediately following the declaration” error
1 2 3 4 5 6 7 8 9 10 11 12 13 | function odFunc(a:string):string function odFunc(a:number,b:string):string let a =10 function odFunc(a:any,b?:any):string { return "" } //Function implementation is missing or not immediately following the declaration.(2391) |
This also causes errors because the implementation function comes first before the overload function.
1 2 3 4 5 6 7 8 9 | function odFunc(a:any,b?:any):string { return "" } function odFunc(a:string):string function odFunc(a:number,b:string):string //Function implementation is missing or not immediately following the declaration.(2391) |
Return types can be different
The return type of the overload signatures can be different. In this example return type either a string or a number. Hence the implementation function returns the compatible type string|number
. You can also use any
or unknown
.
1 2 3 4 5 6 7 8 9 10 | function odFunc(str:string):string function odFunc(num:number):number function odFunc(arg:string|number):string|number { if (typeof arg=="string") return "" return 0 } |
Implementation Signature is not callable
This code does not throw any compiler error, because we are using any
type in the function argument
1 2 3 4 5 6 7 8 9 10 | function odFunc(arg:any):any { if (typeof arg=="string") return "" return 0 } odFunc([1,2,3]) // No Compiler Error |
But adding an overload signature will result in “No overload matches this call” error. This is because once a function has an overload signature declared, you cannot use the implementation function signature to call the function.
1 2 3 4 5 6 7 8 9 10 11 12 | function odFunc(str:string):string function odFunc(num:number):number function odFunc(arg:any):any { if (typeof arg=="string") return "" return 0 } odFunc([1,2,3]) //No overload matches this call |
Order of overload signature in Important
Order of overload signature is important. TypeScript chooses the first matching overload when resolving function calls. Hence we need to put the more specific signatures before the more general signatures
The following example has two overload signatures. The first one takes the unknown as an argument and returns the unknown. The second signature takes a number array and returns a number.
We call the function with a number array, which is our second overload signature. Hence we expect it to return a number. But hovering over the return variable you will see that it is inferred as unknown.
This is because the unknown is a more general type than a number. The number array can be assigned to an unknown and not the other way around. Hence the typescript compiler thinks we are using the first overload signature.
1 2 3 4 5 6 7 8 9 10 | function fn(x: unknown): unknown; function fn(x: number[]): number; function fn(x:any) { return x; } //call the function with a number array and expect a number back var x = fn([1,2,3]); // x is unknown |
Moving the second overload to the first does solve the problem.
1 2 3 4 5 6 7 8 9 | function fn(x: number[]): number; function fn(x: unknown): unknown; function fn(x:any) { return x; } var x = fn([1,2,3]); // x: number |
Method overloading
A function that is part of a class is known as Method. You can create overload signatures in the class just like we did for the standalone functions.
This example shows the add function implemented as a method in the calculator class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class calculator { result:number; constructor() { this.result=0 } //overload signature add(num1:number,num2:number):number add(numArray:number[]):number //function implementation add(arg1:number|number[], arg2?:number):number { this.result= 0; //default is zero. if (Array.isArray(arg1)) { var sum = arg1.reduce(function(a, b){ return a + b; }, 0); this.result= sum; } if (typeof arg1=="number" && typeof arg2=="number") { //number this.result= arg1 + arg2; } return this.result; } } let Calc=new calculator(); Calc.add(10,10) //20 Calc.add([1,2,3,4,5,6]) //21 |