Typing Functions with Overloading, Values, and Arrow Functions
Overloading#
Overloading allows us to to define multiple function signatures for one function. We can think of it as pattern matching based on the input types that a function is called with.
Let's say we have a function prefix()
that takes two arguments: a prefix and a string (or array of strings) that should have the prefix. If we are given a string as the second parameter, our function should return the result of concatenating the prefix to that string. If we are given an array of strings as the second parameter, our function should return an array of strings, where each string is the result of concatenating the prefix to that string.
As a first step towards typing prefix()
, we might try to use union types to achieve what we need:
function prefix(
pre: string,
word: string | string[]
): string | string[] {
if (typeof word === 'string')
return `${pre}${word}`;
return word.map(w => `${pre}${w}`);
}
At first look this function appears to type our function correctly. The problem arises when we try to use the return value as either a string or an array:
const book = prefix('fullstack ', 'TypeScript');
book.substring(10); // Error: string[] does not have 'substring' method
const books = prefix('fullstack ', ['TypeScript', 'React']);
books.push('fullstack Vue'); // Error: string does not have 'push' method
Union return types are not dynamic, so even though we know that book
is type string
and books
is type string[]
, the TypeScript compiler treats the return value statically as a union of string
and string[]
. Any operation that we run on the return value of prefix()
has to work for both string
and string[]
, which is problematic when we want to use one or the other.
Instead of using union types, we define overloads for our prefix()
function to specify dynamic return types based on the input types passed to the function:
// Overload 1: if word is type string, return a string
function prefix(pre: string, word: string): string;
// Overload 2: if word is type string[], return a string[]
function prefix(pre: string, word: string[]): string[];
// Function implementation
function prefix(
pre: string,
word: string | string[],
): string | string[] {
if (typeof word === 'string')
return `${pre}${word}`;
return word.map(w => `${pre}${w}`);
}
const book = prefix('fullstack ', 'TypeScript'); // string
book.substring(10); // OK
const books = prefix('fullstack ', ['TypeScript', 'React']); // string[]
books.push('fullstack Vue'); // OK
Function overloads consist of two parts:
One or more function signatures, which consist of parameter types and a return type. We can read these as: "when given these input type(s), return this output." The argument list can vary in length and type, giving us the flexibility to choose different return types for different argument lengths as well as different argument types.
One function definition. This is the function that has a body. This function's signature must be a superset of all the overloads, meaning that we have to take into account all input types and all return types when annotating our function definiton.
In the example below, we define a function getEmployeeInfo()
whose return type depends on the input object type:
This page is a preview of Beginners Guide to TypeScript