0% completed
Rest Parameters
On This Page
Declaring a Rest Parameter
Rest Parameters with Regular Parameters
Spreading an Array into a Call
Rest Parameters with Other Types
Choosing Between a Rest Parameter and an Array Parameter
A receipt may list 2 prices or 30 prices, and a function with a fixed parameter list cannot accept both. Adding up three prices is simple when three is always the number.
function sumPrices(a: number, b: number, c: number): number { return a + b + c; } console.log(sumPrices(120, 80, 50)); // Output: 250
For four prices, this signature has to change, and it has to change again for five. One alternative is to take a single array, which handles any number of prices but makes every caller wrap the values in brackets.
function sumPrices(prices: number[]): number { return prices.reduce((total, price) => total + price, 0); } console.log(sumPrices([120, 80, 50, 30])); // Output: 280
A rest parameter removes the brackets. The caller writes separate arguments, and the function receives them as one array.
Declaring a Rest Parameter
Three dots before the parameter name make it a rest parameter, and its type is an array type.
function sumPrices(...prices: number[]): number { return prices.reduce((total, price) => total + price, 0); } console.log(sumPrices(120, 80, 50)); // Output: 250 console.log(sumPrices(1, 2, 3, 4, 5)); // Output: 15 console.log(sumPrices()); // Output: 0 sumPrices(120, "80"); // Error
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
The compiler reads number[] and applies its element type, number, to each argument separately, so it reports the exact argument that is wrong. When the program runs, JavaScript collects the arguments into a real array, and prices is an empty array if there are none. The last valid call returns 0 because reduce starts from the initial value 0. Since prices is a real array, methods such as map(), filter() and reduce() all work on it.
Rest Parameters with Regular Parameters
Regular parameters can come first. Each one takes its argument by position, and the rest parameter collects whatever remains.
function formatReceipt(customer: string, ...prices: number[]): string { return `${customer}: ${prices.join(", ")}`; } console.log(formatReceipt("Ada", 120, 80, 50)); // Output: Ada: 120, 80, 50
The picture below shows how the four arguments of this call reach the two parameters. Pay attention to the moment the separate numbers become one array.
The rest parameter must be the last parameter, because it takes all the remaining arguments and leaves none for a parameter after it.
function formatReceipt(...prices: number[], customer: string): string { // Error return `${customer}: ${prices.join(", ")}`; }
error TS1014: A rest parameter must be last in a parameter list.
Two more rules follow from what a rest parameter is. Its type must be an array type or a tuple type, otherwise the compiler reports TS2370: A rest parameter must be of an array type. It also cannot be marked optional, and the compiler reports TS1047: A rest parameter cannot be optional. It does not need to be optional, because no arguments simply give an empty array.
Spreading an Array into a Call
The same three dots appear in a call too, where they do the reverse job. In a parameter list they collect arguments into an array, and that is rest syntax. In a call they expand an array into separate arguments, and that is spread syntax. The two have different names, so do not call a rest parameter a spread operator.
You need spread when the prices are already in an array.
function sumPrices(...prices: number[]): number { return prices.reduce((total, price) => total + price, 0); } const cart = [120, 80, 50]; console.log(sumPrices(...cart)); // Output: 250 console.log(sumPrices(...cart, 30)); // Output: 280 sumPrices(cart); // Error
error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'number'.
The error shows the rule clearly: each argument must be a number, and cart is an array of numbers.
Spreading into a fixed parameter list needs more care, because the compiler does not know how many elements a number[] holds. A tuple type states the length, so the compiler accepts it.
function sumThree(a: number, b: number, c: number): number { return a + b + c; } const cart = [120, 80, 50]; const fixedCart: [number, number, number] = [120, 80, 50]; sumThree(...cart); // Error console.log(sumThree(...fixedCart)); // Output: 250
error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
Rest Parameters with Other Types
The elements of a rest parameter can be of any type. Objects are common, and the function below takes any number of products.
type Product = { name: string; price: number }; function printProducts(...products: Product[]): void { products.forEach((product) => { console.log(`Product: ${product.name}, Price: $${product.price}`); }); } printProducts( { name: "Laptop", price: 1200 }, { name: "Phone", price: 800 }, { name: "Tablet", price: 500 } );
Product: Laptop, Price: $1200 Product: Phone, Price: $800 Product: Tablet, Price: $500
Tuples can be used in two ways. The elements can be tuples, which gives any number of pairs, or the whole rest parameter can be one tuple, which fixes the number and the type of each argument.
function countLines(...lines: [string, number][]): number { return lines.length; } console.log(countLines(["Laptop", 1200], ["Phone", 800])); // Output: 2 function addLine(...line: [string, number]): void { console.log(line); } addLine("Laptop", 1200); // Output: [ 'Laptop', 1200 ] addLine("Laptop"); // Error
error TS2554: Expected 2 arguments, but got 1.
Choosing Between a Rest Parameter and an Array Parameter
The two forms differ for the caller, while the body sees an array in both cases.
| Feature | Rest parameter | Array parameter |
|---|---|---|
| Signature | function fn(...args: number[]) | function fn(args: number[]) |
| Arguments | Many separate arguments | One array argument |
| Example call | fn(1, 2, 3, 4) | fn([1, 2, 3, 4]) |
| Call with an existing array | fn(...list) | fn(list) |
A rest parameter suits calls where a person writes the values by hand. An array parameter suits data that is already in an array, especially large data, for two reasons. Each call with a rest parameter creates a new array, and a spread argument puts every element on the call stack.
The second reason has a hard limit. In our test on Node.js, sumPrices(...new Array(100000).fill(1)) worked, and the same call with 1,000,000 elements failed with this error:
RangeError: Maximum call stack size exceeded
The exact limit depends on the JavaScript engine and its stack size, so do not spread an array whose size you do not control. Finally, give a rest parameter a plural name such as prices or names, so the reader sees that it holds many values.
Reading Progress
0%
On This Page
Declaring a Rest Parameter
Rest Parameters with Regular Parameters
Spreading an Array into a Call
Rest Parameters with Other Types
Choosing Between a Rest Parameter and an Array Parameter