Rest Parameters
Rest parameters let a function accept an indefinite number of arguments and gather them into a single real array. Introduced in ES2015, they replace the awkward arguments object of older JavaScript with cleaner syntax and the full power of array methods. Whenever you write a variadic function — one that can take “however many” values — rest parameters are the modern, readable way to do it.
The syntax
You declare a rest parameter by prefixing the last parameter name with three dots (...). Inside the function it is an ordinary array containing every argument that wasn’t matched by a preceding named parameter.
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3));
console.log(sum(10, 20, 30, 40, 50));
console.log(sum());
Output:
6
150
0
Because numbers is a genuine array, you can immediately call reduce, map, filter, or any other array method without conversion.
Combining with named parameters
A rest parameter scoops up the leftovers, so it must come last in the parameter list. Place fixed, named parameters first and let the rest gather everything after them.
function logEvent(level, ...messages) {
const tag = `[${level.toUpperCase()}]`;
console.log(tag, messages.join(" "));
}
logEvent("info", "Server", "started", "on", "port", 3000);
logEvent("error", "Connection refused");
Output:
[INFO] Server started on port 3000
[ERROR] Connection refused
Here level binds to the first argument and messages collects the rest. Putting a parameter after a rest parameter is a syntax error.
Gotcha: You can have only one rest parameter, and it must be the final one.
function f(...a, b) {}andfunction f(...a, ...b) {}both throw aSyntaxError.
Rest parameters vs. the arguments object
Before ES2015, the only way to read extra arguments was the array-like arguments object. Rest parameters are superior in almost every way.
| Aspect | Rest parameters | arguments object |
|---|---|---|
| Type | A real Array | Array-like (no array methods) |
| Contents | Only args beyond named params | Every argument passed |
| Arrow functions | Works | Not available |
Array methods (map, etc.) | Direct | Requires conversion first |
| Readability | Named and explicit | Implicit, easy to miss |
The biggest practical difference: arrow functions have no arguments binding at all, so rest parameters are your only option there.
const collect = (...items) => items;
console.log(collect("a", "b", "c"));
// Converting arguments the old way (regular function only):
function legacy() {
const args = Array.prototype.slice.call(arguments);
return args;
}
console.log(legacy(1, 2, 3));
Output:
[ 'a', 'b', 'c' ]
[ 1, 2, 3 ]
Rest vs. spread
The ... token does two opposite jobs depending on where it appears. As a rest parameter (in a function definition) it gathers values into an array. As a spread operator (in a function call or literal) it expands an array into individual values. They pair naturally:
function max(...values) {
return Math.max(...values); // spread the array back out
}
const scores = [42, 17, 99, 3];
console.log(max(...scores));
Output:
99
Read it as: scores is spread into separate arguments at the call site, then max rests them back into the values array, then they are spread again into Math.max.
A practical variadic helper
Rest parameters shine for utility functions that operate over an arbitrary list. Here is a small pipe-style composer:
function pipe(...fns) {
return (input) => fns.reduce((acc, fn) => fn(acc), input);
}
const double = (n) => n * 2;
const increment = (n) => n + 1;
const square = (n) => n * n;
const transform = pipe(double, increment, square);
console.log(transform(3)); // ((3*2)+1)^2
Output:
49
You can also destructure rest into the parameter list to peel off a head and keep a tail:
function describe([first, ...others]) {
return `First: ${first}, remaining: ${others.length}`;
}
console.log(describe([10, 20, 30, 40]));
Output:
First: 10, remaining: 3
Best Practices
- Reach for rest parameters instead of
argumentsin all new code — they are typed, named, and work in arrow functions. - Keep the rest parameter last and use clear plural names like
...itemsor...handlersto signal intent. - Combine fixed named parameters up front with a rest parameter for the variable tail when the first few arguments have specific meanings.
- Remember the directions: rest gathers in definitions, spread expands in calls — they are mirror images of the same
...syntax. - Avoid relying on rest parameters in
.lengthchecks;function f(...args) {}reportsf.lengthas0. - Prefer spreading an existing array (
fn(...arr)) overfn.apply(null, arr)for clarity.