Skip to content
JavaScript js functions 4 min read

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) {} and function f(...a, ...b) {} both throw a SyntaxError.

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.

AspectRest parametersarguments object
TypeA real ArrayArray-like (no array methods)
ContentsOnly args beyond named paramsEvery argument passed
Arrow functionsWorksNot available
Array methods (map, etc.)DirectRequires conversion first
ReadabilityNamed and explicitImplicit, 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 arguments in all new code — they are typed, named, and work in arrow functions.
  • Keep the rest parameter last and use clear plural names like ...items or ...handlers to 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 .length checks; function f(...args) {} reports f.length as 0.
  • Prefer spreading an existing array (fn(...arr)) over fn.apply(null, arr) for clarity.
Last updated June 1, 2026
Was this helpful?