Skip to main content

Command Palette

Search for a command to run...

Spread vs Rest Operators in JavaScript

Expand what you have, collect what you need — mastering spread and rest operators in JavaScript

Published
6 min read

Both operators are written as three dots — ... — yet they do completely opposite things depending on where you place them. This single syntactic choice led to one of the most common points of confusion in modern JavaScript. The good news: once you understand the core mental model, it clicks permanently.

💡
Spread expands a collection into individual pieces. Rest gathers individual pieces into a collection.

Think of it like breathing: spread is exhaling — pushing values outward into a space — while rest is inhaling — pulling values inward into a container. The dots are the same; the direction of data is opposite.



Chapter 01

The Spread Operator


The spread operator takes an iterable — an array, string, Set, or any object with [Symbol.iterator] — and expands its contents in-place. You use it wherever a list of values is expected.

Spreading into Arrays

const fruits = ['apple', 'banana'];
const veggies = ['carrot', 'daikon'];

// Combine arrays — no .concat() needed
const grocery = [...fruits, ...veggies];
// ['apple', 'banana', 'carrot', 'daikon']

// Clone an array (shallow copy)
const copy = [...fruits];

// Insert in the middle
const mixed = ['lemon', ...fruits, 'fig'];
// ['lemon', 'apple', 'banana', 'fig']

Spreading into Function Calls

Before spread, passing an array to a function expecting individual arguments required .apply(). Now it's effortless:

const scores = [42, 91, 17, 68];

// Old way
Math.max.apply(null, scores);

// New way — clean and readable
Math.max(...scores);  // 91

// Works with any function
function greet(name, role) {
  return `Hello \({name}, the \){role}!`;
}
const args = ['Ada', 'engineer'];
greet(...args);  // 'Hello Ada, the engineer!'

Spreading Objects

Object spread (ES2018) copies enumerable own properties from one object into another — the cleaner alternative to Object.assign():

const defaults = { theme: 'light', lang: 'en', grid: 12 };
const userPrefs = { theme: 'dark', lang: 'fr' };

// Merge with override — later keys win
const config = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'fr', grid: 12 }

// Shallow clone an object
const snapshot = { ...config };

// Add / override specific properties
const updated = { ...config, grid: 16 };
// { theme: 'dark', lang: 'fr', grid: 16 }

⚠️ Shallow Only

Spread creates a shallow copy. Nested objects and arrays are still shared by reference. If you need a deep clone, use structuredClone(obj) or a library like Lodash.



Chapter 02

The Rest Operator


Rest does the inverse. It collects multiple individual values into a single array. You see it in two places: function parameter lists and destructuring assignments.

Rest in Function Parameters

When you don't know how many arguments a function will receive, rest gathers the extras into a real array — replacing the old, awkward arguments object:

// Sum any number of arguments
function sum(...nums) {
  return nums.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3);       // 6
sum(10, 20, 30, 40); // 100

// Mix named params with rest
function logger(level, ...messages) {
  messages.forEach(msg =>
    console.log(`[\({level}] \){msg}`)
  );
}
logger('INFO', 'Server started', 'Port 3000', 'PID 1042');

📌 Rest Must Be Last

The rest parameter must always be the final parameter in the list. Writing function bad(...items, last) is a syntax error — rest collects everything remaining, so nothing can follow it.

Rest in Array Destructuring

const [first, second, ...remaining] = [10, 20, 30, 40, 50];

console.log(first);     // 10
console.log(second);    // 20
console.log(remaining); // [30, 40, 50]

// Useful for grabbing a queue's head
const [head, ...tail] = taskQueue;

Rest in Object Destructuring

const user = {
  id: 7,
  name: 'Alan',
  password: 's3cr3t',
  role: 'admin'
};

// Pluck what you need; collect the rest
const { password, ...safeUser } = user;

console.log(safeUser);
// { id: 7, name: 'Alan', role: 'admin' }

// safeUser is now safe to expose publicly


Chapter 03

Side-by-Side Comparison


Property Spread ... Rest ...
Direction Expands outward Collects inward
Used in Function calls, array literals, object literals Function parameters, destructuring
Input type Iterable or object Multiple individual values
Output type Individual values An Array
Position rule Anywhere in the list Must be last
Count Use multiple times Only once per signature
💡
Quick Mental Check : Ask yourself: Am I putting values into something? → Spread. Am I catching values from something? → Rest. The direction of data flow is your guide.


Chapter 04

Real-World Use Cases


Putting It All Together

Here's a realistic example that uses both operators in a single function — a common pattern in component-based UI code:

// REST: collect all extra props not explicitly named
function Button({ label, variant = 'primary', ...rest }) {
  const baseStyles = ['btn', `btn-${variant}`];

  // SPREAD: expand baseStyles into a new array
  const classes = [...baseStyles, rest.className]
    .filter(Boolean)
    .join(' ');

  // SPREAD: forward all remaining props to the element
  return `<button class="\({classes}">\){label}</button>`;
}

const extraProps = { disabled: true, id: 'submit' };

// SPREAD: unpack extraProps into named arguments
Button({ label: 'Save', variant: 'danger', ...extraProps });


Chapter 05

Common Gotchas

Spread on Non-Iterables

Spreading a plain object into an array literal will throw a TypeError because plain objects don't implement the iterable protocol. Object spread only works inside {}.

const obj = { a: 1 };

[...obj]          // ❌ TypeError: obj is not iterable
({ ...obj })      // ✅ { a: 1 }   — object spread works fine

// Strings ARE iterable
[...'hello']      // ✅ ['h','e','l','l','o']

Rest Doesn't Work Twice

You can only have one rest element per destructuring pattern, and it must be last. Multiple rests in the same assignment is a syntax error.

const [...a, last] = [1,2,3];       // ❌ Rest must be last
const [...a, ...b] = [1,2,3];     // ❌ Only one rest allowed
const [x, y, ...rest] = [1,2,3]; // ✅ Correct


Takeaway

The One Rule to Remember


Position determines purpose. When ... appears where values are consumed (function arguments, array literals, object literals), it is Spread. When ... appears where values are defined or captured (function parameters, destructuring patterns), it is Rest.

Master this distinction and you'll read and write modern JavaScript with far more confidence — and far fewer head-scratching moments at 2 AM.

💡
Same three dots. Two completely different jobs. One simple rule: are you giving or receiving?