How to Overload Functions in Typescript
In this issue, we talk about function overloading and how to do function overloading to create types for complex functions, without compromising type safety.
I have a question: How do you provide types for a function that can be called in multiple ways? Let’s take a simple example: a function that accepts either a string or a string array and returns a string if the input was a string; otherwise, it returns a string array if the input was an array.
One way to design types for the afro-mentioned function is shown below:
function doSomething(input: string | string[]): string | string[] {
// implementation details
}
Unfortunately, this isn’t type safe because, while the above function captures the multiple ways the above function can be called, i.e., it can take in different inputs and return different outputs, it doesn’t accurately portray our intention, i.e., the input is tied to the output.
Types are a declaration of intention, Typescript will check the code we write against our intention, this is why we need to ensure that our types matchup to our intention as accurately as possible.
Since the above function allows a string to return a string array and vice versa, this is not what we intended, and it will also allow invalid states within our codebase, which we try to avoid when designing types.
So, you may be wondering, what’s the solution for this? The answer is Function Overloading; I guess you may have figured out this by the title of this issue.
What is Function Overloading
Function overloading allows us to declare multiple function/method signatures for a single function. If we take our example above; these are the possible function signature for our function:
// function signatures definition
function doSomething(input: string): string
function doSomething(input: string[]): string[]
// implementation function
function doSomething(input: string | string[]): any {
// implementation details
if(typeof input === "string") {
// do something with string
} else {
// do something with array input
}
}
As you can see from the above example, we are defining two function signatures, each with its specific input and output. If we take our example above; these are the possible function signature for our function: now, when we call our doSomething
function with a string, Typescript can infer the return type based on the appropriate function overload.
const res = doSomething("string");
// ^? const res: string
And the same applies when we call the function using an array as input:
const res2 = doSomething(["string", "array"])
// ^? const res2: string[]
There are a few things to remember about the implementation function (the function that we will implement the actual code for our function) of a function overload.
First, it needs to be general, i.e., the implementation function signature types must be wide enough to encompass each function overload signature; otherwise, Typescript will reject the function overload.
function doSomething(input: string): string
function doSomething(input: string[]): string[]
function doSomething(input: string | string[]): string {
// implementation details
if(typeof input === "string") {
// do something with string
} else {
// do something with array input
}
}
In the above example, we are going to get the following error:
This overload signature is not compatible with its implementation signature.
And secondly, our implementation function isn’t callable; the function overload signatures are the ones that can be called. As shown below, the implementation function signature is hidden from you when calling doSomething()
.
What about Methods?
In this issue, I have spent most of the time focussing on functions, and you may be wondering, can I do the same with methods? And the answer to the question is yes, you can, as shown below:
class Person {
sayHello(input: string): string
sayHello(input: string[]): string[];
sayHello(input: string | string[]) : any {
// implementation here
}
}
Overloading methods call signatures also follow the same rules as the overloading function call signatures above. The only difference is that we are overloading methods within a class or an object.
To make method overloading with objects, we would need to define a type with the method overloading we need, as shown below:
interface IPerson {
sayHello(input: string): string
sayHello(input: string[]): string[];
}
const person: IPerson = {
sayHello(input: string | string[]) : any {
// do something
}
}
const res = person.sayHello("hello")
// ^? const res: string
const res2 = person.sayHello(["string", "array"])
// ^? const res2: string[]
You can use type as well:
type IPerson = {
sayHello(input: string): string
sayHello(input: string[]): string[];
}
Should you use Function Overloading?
Now that we know about Function overloading, this begs the question, should you use it? Like every feature in Typescript, the answer is it depends. If you are defining a complex function signature, the answer is yes, but it’s not recommended for the following scenarios:
Don’t write several overloads that differ only in trailing parameters. Instead, use optional parameters. For more information, check out this detailed example in the Typescript docs.
Don’t write overloads that differ by type in only one argument position. For more information, check out this example here.
Conclusion
In this issue, we covered function overloading, which allows us to express complex function signatures while ensuring type safety. We learned how we could define multiple function signatures for functions to create function overloads for complex function signatures in Typescript. On top of that, we saw how we could implement method overloads in both classes and objects in Typescript.
And that’s it from me, I hope you enjoyed ❤️ this issue as much as I did writing it, and I hope you have a fantastic week ahead.
Please consider doing one of the following things if you love this issue.
1. Share it ❤️ - Help spread word of mouth and help All Things Typescript grow:
2. ✉️ Subscribe to this newsletter. You will receive weekly content like this from All Things Typescript that will help you learn and master Typescript.
3. Consider Sponsoring me on GitHub ❤️; you will be helping me create more free content for the benefit of everyone.
Au revoir 👋🏿.