Skip to content

The Rest Parameter

Spread expands a collection outward. Rest collects individual items inward. They use the same ... syntax but in opposite positions.

A rest parameter collects all remaining function arguments into an array:

function logAll(...items) {
items.forEach(item => console.log(item));
}
logAll('Coffee', 'Lunch', 'Dinner');
// 'Coffee'
// 'Lunch'
// 'Dinner'

items is a real array — you can call forEach, map, filter, and any other array method on it.

Rest parameters can follow named parameters. They collect everything that was not already captured:

function logExpenses(label, ...expenses) {
console.log(`${label}:`);
expenses.forEach(e => console.log(` ${e.description}: $${e.amount.toFixed(2)}`));
}
logExpenses('Food expenses', coffeeExpense, lunchExpense, dinnerExpense);
// 'Food expenses:'
// ' Coffee: $4.50'
// ' Lunch: $12.75'
// ' Dinner out: $38.20'

The rest parameter must always be last — you cannot put a parameter after ...rest.

In object destructuring, rest collects all properties that were not explicitly extracted:

const expense = { id: 1, description: 'Coffee', amount: 4.50, category: 'Food', date: '2024-01-15' };
const { id, ...rest } = expense;
console.log(id); // 1
console.log(rest); // { description: 'Coffee', amount: 4.50, category: 'Food', date: '2024-01-15' }

The variable id gets its value; everything else goes into rest as a new object.

This is the pattern used in the RecurringExpense constructor from Module 02:

constructor({ recurrence, ...rest }) {
super(rest); // passes everything except recurrence to the parent constructor
this.recurrence = recurrence;
}

The subclass pulls out what it specifically needs (recurrence) and passes the remainder to super — no need to list every parent property explicitly.

[first, ...remaining] collects everything after the first element:

const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

Spread vs rest — the same syntax, opposite meaning

Section titled “Spread vs rest — the same syntax, opposite meaning”
PositionNameWhat it does
Function call: fn(...arr)SpreadExpands array into arguments
Function parameter: fn(...args)RestCollects arguments into array
Assignment right side: { ...obj }SpreadCopies/merges properties out
Assignment left side: const { a, ...rest } = objRestCollects remaining properties

The rule: spread is on the right (expanding out), rest is on the left (collecting in).

When you want to update an expense’s description and amount but preserve all other fields, use rest to separate the changed fields from the unchanged ones:

function applyUpdate(expense, updates) {
const { id, createdAt, ...mutableFields } = expense;
const updatedFields = { ...mutableFields, ...updates };
return { id, createdAt, ...updatedFields };
}

id and createdAt are protected — they are pulled out first and then explicitly put back, so updates cannot accidentally overwrite them even if it contains those keys.

  1. Write a function sum(...numbers) using a rest parameter that returns the total of all arguments. Call it with 3, 5, and 7 separate arguments.
  2. Destructure id and createdAt from an expense object and collect the remaining fields into fields.
  3. Write applyUpdate(expense, updates) as shown above and test it by changing amount without touching id.
  4. Use [first, ...rest] to split the BudgetBuddy expenses array into the first expense and the remaining ones.
  • ...rest in function parameter position collects remaining arguments into an array.
  • const { a, ...rest } = obj collects all properties not explicitly extracted.
  • const [first, ...rest] = arr collects all elements after the first.
  • Rest must always be the last item in a parameter or destructuring pattern.
  • Same syntax (...) as spread, but rest is on the left (collecting), spread is on the right (expanding).