This article will explain function overloads in TypeScript using real-world examples and show you exactly how they’re used. We’ll even include TypeScript playground links for our examples. This way, you can quickly try them yourself. By the end, you’ll know how to use union types like the pros.
What are Function Overloads?
Function overloads are a concept in programming languages that allows you to overload a function with different call signatures. That means you can define the same function in more than one way. Here’s an example from the TypeScript Handbook:
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
As you can see, the name stays the same, but the number of arguments and the meaning change in each variation. This can be handy if you are writing a function that accepts various types of arguments.
Later, we’ll take a look at some examples from open source and then explain when you should use them.
Show me the code
If you want to jump straight into code examples, take a look at these TypeScript playground links:
- makeDate example - TypeScript Playground link
- useParam example - Blitz.js source code
- isDiv example - TypeScript Playground link
Examples of Function Overloads in Open Source
Function overloads are most commonly used in libraries like DOM, lodash, Blitz, and more, to name a few. Here’s an example from Blitz.js (link to source code):
export function useParam(key: string): undefined | string | string[];
export function useParam(key: string, returnType: "string"): string | undefined;
export function useParam(key: string, returnType: "number"): number | undefined;
export function useParam(
key: string,
returnType: "array"
): string[] | undefined;
export function useParam(
key: string,
returnType?: ReturnTypes
): undefined | number | string | string[] {
const params = useParams(returnType);
const value = params[key];
return value;
}
Let’s break down what’s happening. We have a function called useParam. Key is always required and always a string. Still, returnType can be optional and a variety of different types, even a custom one called ReturnTypes (definition omitted for simplicity but defined here). There are five overload signatures, each with its parameters and return types.
And we can see how it’s used by looking at these tests. Here is one example that I’ve slimmed down:
describe("useParam", () => {
it("works with number", () => {
// This is the router query object which includes route params
const query = {
id: "1",
};
let { result } = renderHook(() => useParam("id", "number"), {
router: { query },
});
expect(result.current).toEqual(1);
});
});
In this function call, useParam matches this function overload:
export function useParam(key: string, returnType: "number"): number | undefined;
We ask for a param with the key equal to "id", and then we want the return type as "number" to get 1 back. Pretty neat to learn about function overloads and see them used in a real project!
More Use Cases in Open Source
Sometimes it’s helpful to look at other examples when you need inspiration. Since I didn’t have time to break down every example, I leave you with a list:
- lodash - .fill()
- Blitz - use-param
- React Query - useQuery
- Redux - Connect
- Docusaurus - usePluginData
- next-auth
- DOM:
- Sourcegraph search
Shout-out to the TypeScript community on Twitter for helping me crowd-source these examples:
When should you use them?
It depends! Some folks (myself included) believe you can get by without them.
Most of the time, union types solve the problem. As John Reilly points out, they’re also easier to understand and closer to JS. Most of the examples we shared from the open source were from libraries. Therefore, if you’re building a typical production application, you might not need them.
Ordering Matters
If you decide to use them, remember that the order of your overloads matters. Put more specific overloads first:
function isDiv(x: HTMLDivElement): boolean;
function isDiv(x: HTMLElement): boolean;
function isDiv(x: unknown): boolean {
if (x instanceof HTMLDivElement) {
return true;
}
return false;
}
Here, I order them by specificity: HTMLDivElement → HTMLElement → unknown. This is because TypeScript uses the first matching overload. Otherwise, it defaults to this “more general” one and never finds the other ones. Read more here.
Resources
For more information on function overloads, take a look at these resources:
- TypeScript Handbook
- Tweet
- TypeScript: The humble function overload
- Overloaded functions in TypeScript
- What are Function overloads in TypeScript
- YouTube - Master function overloads with compose
Summary
We learned about function overloads and how they can be used to define multiple call signatures for your functions in TypeScript. They’re most often used in libraries like lodash and Redux.
This new knowledge will serve you in understanding private codebases at companies you might work with in the future and open source libraries you might use. Now go out and write some TypeScript!