Rest Parameters

Collect any number of arguments into a real array with ...rest.

Basics

...rest collects remaining arguments into an array.

// Collect all arguments:
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3);       // 6
sum(1, 2, 3, 4, 5); // 15
sum();              // 0

// 'numbers' is a real Array:
function show(...args) {
  console.log(Array.isArray(args)); // true
  console.log(args.length);
  args.forEach(a => console.log(a));
  args.map(a => a * 2); // all array methods work!
}

// Accepts any number of arguments:
function log(...messages) {
  messages.forEach(msg => console.log(msg));
}
log("hello");
log("a", "b", "c", "d");

vs arguments

Rest parameters replace the old arguments object.

// Old way — arguments object:
function old() {
  console.log(arguments[0]); // works
  arguments.forEach(x => x); // ❌ TypeError!
  // arguments is array-LIKE, not a real array
  
  // Had to convert:
  var args = Array.prototype.slice.call(arguments);
  args.forEach(x => console.log(x)); // now works
}

// New way — rest parameters:
function modern(...args) {
  console.log(args[0]); // works
  args.forEach(x => console.log(x)); // works!
  args.map(x => x * 2); // works!
  // It's already a real array
}

Rest parameters give you a real Array with all methods. The old arguments object was array-like but not an actual Array.

1 / 2

With Other Params

Combine named parameters with rest — rest must be last.

// Named params + rest (rest must be LAST):
function tag(name, ...classes) {
  return `<${name} class="${classes.join(" ")}">`;
}
tag("div", "container", "flex", "p-4");
// '<div class="container flex p-4">'

// First arg is special, rest collected:
function printf(template, ...values) {
  return template.replace(/%s/g, () => values.shift());
}
printf("Hello %s, you have %s items", "Alice", 3);
// "Hello Alice, you have 3 items"

// ❌ Rest must be the last parameter:
function bad(...rest, last) {} // SyntaxError!
function bad(...a, ...b) {}   // SyntaxError! (only one rest)

// ✓ These work:
function ok(a, b, ...rest) {}
function ok(...all) {}

In Destructuring

Rest works in array and object destructuring too.

// Array destructuring — collect remaining:
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first = 1, second = 2, rest = [3, 4, 5]

const [head, ...tail] = "hello".split("");
// head = "h", tail = ["e", "l", "l", "o"]

// Object destructuring — collect remaining properties:
const { name, age, ...details } = {
  name: "Alice", age: 25, city: "Tokyo", role: "dev"
};
// name = "Alice", age = 25
// details = { city: "Tokyo", role: "dev" }

// Remove properties immutably:
const { password, ...safeUser } = user;
// safeUser has everything except password

// Nested:
const { data: { id, ...metadata }, ...response } = apiResult;

// In function parameters:
function UserCard({ name, avatar, ...rest }) {
  // rest = all other props
  return <div {...rest}>{name}</div>;
}

Patterns

Common real-world uses of rest parameters.

// 1. Wrapper/decorator functions:
function withLogging(fn) {
  return function(...args) {
    console.log(`Calling ${fn.name} with`, args);
    const result = fn(...args); // spread back out!
    console.log(`Result:`, result);
    return result;
  };
}
const loggedAdd = withLogging((a, b) => a + b);
loggedAdd(2, 3); // logs args, returns 5

// 2. Pipe/compose:
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
const process = pipe(
  x => x * 2,
  x => x + 1,
  x => `Result: ${x}`
);
process(5); // "Result: 11"

// 3. Event emitter:
class EventBus {
  #handlers = {};
  on(event, fn) { (this.#handlers[event] ??= []).push(fn); }
  emit(event, ...args) {
    this.#handlers[event]?.forEach(fn => fn(...args));
  }
}

// 4. Partial application:
function partial(fn, ...presetArgs) {
  return (...laterArgs) => fn(...presetArgs, ...laterArgs);
}
const add10 = partial((a, b) => a + b, 10);
add10(5); // 15

FAQ

Common questions about rest parameters.