Comparison

JavaScript Spread vs Rest: Same Syntax, Different Jobs

Master JavaScript's ... operator. Learn when spread expands values and when rest collects them, with examples, gotchas, and deep clone alternatives.

JavaScript Spread vs Rest: Same Syntax, Different Jobs

The three dots (...) in JavaScript wear two hats. Spread takes an iterable or object and expands it into individual pieces. Rest does the opposite: it collects loose pieces into a single array or object. The syntax is identical; the direction of data flow is what changes.

The rule of thumb

If ... appears on the right side of an assignment or inside a function call, array literal, or object literal, it is spreading. If it appears on the left side (a parameter list or destructuring pattern), it is resting.

Where each one lives

ContextOperatorDirection
Function call: fn(...args)SpreadExpands into arguments
Array literal: [...a, ...b]SpreadExpands into elements
Object literal: {...a, ...b}SpreadExpands into properties
Function parameter: function fn(...args)RestCollects into array
Array destructuring: const [a, ...rest] = arrRestCollects remaining items
Object destructuring: const {a, ...rest} = objRestCollects remaining keys

Spread in action

// Expand array into function arguments
const nums = [3, 1, 4, 1, 5];
Math.max(...nums); // 5

// Merge arrays
const combined = [...nums, 9, 2, 6];

// Clone and extend an object
const user = { name: 'Ada', role: 'admin' };
const updated = { ...user, role: 'owner' }; // right wins
// { name: 'Ada', role: 'owner' }

When object spread encounters duplicate keys, the rightmost value wins. This is how patch-style updates work in Redux reducers and React state setters.

Rest in action

// Collect any number of arguments
function sum(...values) {
  return values.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10

// Split head from tail
const [first, ...rest] = ['a', 'b', 'c', 'd'];
// first = 'a', rest = ['b', 'c', 'd']

// Strip a field from an object
const { password, ...safeUser } = user;

Rest has one hard rule: it must be the last parameter or destructured slot. Writing function fn(...args, last) is a syntax error.

The shallow copy gotcha

Spread creates a copy, but only one level deep. Nested objects and arrays are still shared by reference.

const original = { name: 'Ada', address: { city: 'London' } };
const copy = { ...original };

copy.address.city = 'Paris';
console.log(original.address.city); // 'Paris' - mutated!

The top-level address reference was copied, not the object it points to. Both objects now share the same inner address.

Deep clone alternatives

Three options, from oldest to best:

// 1. JSON round-trip (legacy trick)
const deep1 = JSON.parse(JSON.stringify(original));
// Loses: functions, undefined, Date (becomes string),
// Map, Set, RegExp, cyclic references (throws)

// 2. structuredClone (modern, recommended)
const deep2 = structuredClone(original);
// Handles: Date, Map, Set, RegExp, TypedArrays, cycles
// Does not clone: functions, DOM nodes, class prototypes

// 3. Manual spread for known shapes
const deep3 = {
  ...original,
  address: { ...original.address }
};

structuredClone ships in every modern browser and in Node 17+. Reach for it first. Use the manual spread pattern when you only need to isolate one or two nested branches and want to keep the copy cheap.

Quick mental model

  • Spread unpacks a container so its contents can be reused elsewhere.
  • Rest packs loose values into a container so they can be handled as a group.
  • Same three dots, opposite directions, always shallow at the boundary they operate on.
spread operator rest parameters javascript ... shallow copy object spread structuredClone destructuring es6

Explore 300+ Free Tools

Utilko has tools for developers, writers, designers, students, and everyday users — all free, all browser-based.