Last updated: Feb 27, 2024
Reading timeยท5 min
To return multiple values from a function in TypeScript, group the values in
an array and return the array, e.g. return [myValue1, myValue2] as const
.
You can then destructure and use the values the function returns.
function getValues() { const str = 'bobbyhadz.com'; const num = 100; return [str, num] as const; } // ๐๏ธ const result: readonly ["bobbyhadz.com", 100] const result = getValues(); const [str, num] = getValues(); console.log(str.toUpperCase()); // ๐๏ธ "BOBBYHADZ.COM" console.log(num.toFixed(2)); // 100.00
If you need to define a function with multiple return types, click on the following subheading:
We declared a function that returns multiple values by grouping them in an array.
Notice that the type of the result
variable (and the function's return type)
is ["bobbyhadz.com", 100]
.
The as const
syntax is called a const assertion
in TypeScript.
The const assertion sets the function's return type to be
readonly ["bobbyhadz.com", 100]
, which is exactly what we want.
Let's look at the function's return type if we omit the const assertion.
function getValues() { const str = 'bobbyhadz.com'; const num = 100; return [str, num]; } // ๐๏ธ const result: (string | number)[] const result = getValues(); const [str, num] = getValues(); // ๐๏ธ Now str is string or number // and num is `string` or `number`
Without the as const
syntax, the function is typed to return an array
containing strings or numbers.
This isn't great, because when we
destructure
the values from the array into the str
and num
variables, they are typed as
string | number
.
The string | number
syntax is called a union type in TypeScript. A union type
is formed by combining two or more other types.
I've written a detailed article on how to define an array with multiple types in TS.
You could get around this by explicitly setting the function's return type to a
tuple containing a string
and a
number
.
function getValues(): [string, number] { const str = 'bobbyhadz.com'; const num = 100; return [str, num]; } // ๐๏ธ const result: [string, number] const result = getValues(); const [str, num] = getValues();
We've explicitly typed the function to return a tuple where the first element is a string and the second is a number.
str
and num
variables, they are typed correctly.The syntax with the square brackets on the left-hand side of the assignment is called destructuring.
const [a, b] = ['bobby', 'hadz']; console.log(a); // ๐๏ธ "bobby" console.log(b); // ๐๏ธ "hadz"
An easy way to think about it is that we are assigning the elements of the array to variables. Note that the order of assignment and the order of the values in the array is the same.
If you don't want to use destructuring, you can explicitly access the values by using bracket notation.
function getValues() { const str = 'bobbyhadz.com'; const num = 100; return [str, num] as const; } // ๐๏ธ const result: [string, number] const result = getValues(); const str = result[0]; const num = result[1];
Use a union type to define a function with multiple return types in TypeScript.
function getValue(num: number): string | number { if (num > 5) { return 100; } return 'bobbyhadz.com'; } // ๐๏ธ const result1: string | number const result = getValue(4); if (typeof result === 'string') { // ๐๏ธ result is a string here console.log(result.toUpperCase()); } else { // ๐๏ธ result is a number here console.log(result.toFixed(2)); }
The function must return a value that is represented in the union type, otherwise, the type checker throws an error.
You can use the same approach when working with arrow functions.
const getValue = (num: number): string | number => { if (num > 5) { return 100; } return 'bobbyhadz.com'; };
You can include as many types as necessary in your union. Simply separate the
types with a pipe |
.
const getValue = (num: number): string | number | boolean => { if (num === 0) { return true; } if (num > 5) { return 100; } return 'bobbyhadz.com'; };
The function in the example above returns a string
or number
.
The same approach can be used with type aliases, interfaces or classes.
class Animal { run() { console.log('the animal runs'); } } class Human { walk() { console.log('the human walks'); } } function getValue(num: number): Animal | Human { if (num > 5) { const animal = new Animal(); return animal; } const human = new Human(); return human; }
Here is an example of a function that returns a string array or a numeric array.
function getValue(num: number): string[] | number[] { if (num > 5) { return [100]; } return ['bobbyhadz.com']; }
The functions in the examples have multiple return types, so we have to use a type guard before accessing type-specific methods or properties.
function getValue(num: number): string | number { if (num > 5) { return 100; } return 'bobbyhadz.com'; } // ๐๏ธ const result1: string | number const result = getValue(4); if (typeof result === 'string') { // ๐๏ธ result is a string here console.log(result.toUpperCase()); } else { // ๐๏ธ result is a number here console.log(result.toFixed(2)); }
The result
variable stores a string
or a number
, so TypeScript won't allow
us to access a string-specific built-in method like toUpperCase
until we can
narrow the type down.
The type of
the value stored in result
is a string in the if
block.
2
possible types, so TypeScript knows that the type of the value is a number
in the else
block.How you narrow the type down might be different depending on the type of the values the function returns.
For example, if you had to
check for an array, you'd use
Array.isArray(myArray)
and if you had to check if a value is an instance of a
specific class, you'd use myInstance instanceof myClass
.
A quick and dirty way to get around having to check the type is to use a type assertion.
function getValue(num: number): string | number { if (num > 5) { return 100; } return 'bobbyhadz.com'; } // ๐๏ธ const result1: string const result = getValue(4) as string; // ๐๏ธ type assertion console.log(result.toUpperCase()); // ๐๏ธ "bobbyhadz.com"
The as
keyword is a type assertion and sets the type of the result
variable
to string
without us having to use a type guard.
This approach should generally be avoided as it isn't type-safe and would cause
a runtime error if the result
variable stored a number
.
You can learn more about the related topics by checking out the following tutorials: