Skip to main content

Spread and REST operators in JavaScript: differences and examples

Spread (...) and rest (...) share the same syntax but do opposite things. Spread unpacks an iterable into separate elements; rest collects separate elements into one array.

Theory

TL;DR

  • Spread is like dumping a bag of ingredients onto the counter: everything comes out separately
  • Rest is like scooping leftovers back into the bag: everything goes in together
  • Main difference: spread lives on the right side (or inside a function call), rest lives on the left (in params or destructuring)
  • Copying, merging, or passing data? Use spread. Capturing extra args or remaining props? Use rest
  • Rest always returns an array, even when no extra elements are passed (returns [])

Quick Example

javascript
// Spread: unpacks array into separate elements const nums = [1, 2, 3]; const more = [...nums, 4, 5]; // [1, 2, 3, 4, 5] // Spread in a function call function add(a, b, c) { return a + b + c; } add(...nums); // 6 // Rest: collects remaining args into an array function sum(first, ...rest) { return first + rest.reduce((a, b) => a + b, 0); } sum(1, 2, 3, 4); // 10, rest = [2, 3, 4]

Context decides which one you get. Same three dots, different roles.

Key Difference

Spread appears on the right side of assignments or inside function calls. It breaks an iterable apart and gives you individual values. Rest appears on the left in function parameters or destructuring patterns. It gathers whatever is left over and wraps it in an array. One expands, the other compresses.

When to Use

  • Copy an array without mutation: [...arr]
  • Merge objects: { ...obj1, ...obj2 }
  • Pass an array as separate arguments to a function: func(...arr)
  • Capture a variable number of function arguments: function fn(a, ...rest) {}
  • Destructure with remaining properties: const { x, ...rest } = obj

Comparison Table

FeatureSpread (...)Rest (...)
PositionRight side (assignments, calls)Left side (params, destructuring)
EffectExpands into individual elementsCollects remainders into array
In functionsfunc(...arr) passes separate argsfunc(a, ...rest) gathers extra args
Arrays[...arr1, ...arr2] mergesconst [first, ...rest] = arr splits
Objects (ES2018+){ ...obj1, ...obj2 } mergesconst { a, ...rest } = obj extracts
When to useCopy, merge, pass elementsVariable args, remaining props

How It Works

JavaScript engines treat ... as syntax sugar: spread iterates the source via Symbol.iterator (for arrays) or Object.keys (for objects), yielding values one by one. Rest scans the parameter list at call time and packages trailing arguments into a new array. Both create shallow copies, so nested objects share references with the original.

In React codebases, spread is everywhere. The pattern { ...state, updatedField: newValue } is how reducers stay immutable without pulling in extra libraries.

Common Mistakes

Passing an array to a rest function without spreading it first:

javascript
function add(...nums) { return nums.reduce((a, b) => a + b); } add([1, 2, 3]); // [1,2,3] becomes the first arg, returns NaN add(...[1, 2, 3]); // correct: 6

Assuming spread does a deep copy:

javascript
const a = { nested: { x: 1 } }; const b = { ...a }; a.nested.x = 99; console.log(b.nested.x); // 99 - nested object is shared! // Fix: spread the nested object too const c = { ...a, nested: { ...a.nested } };

Spreading a non-iterable:

javascript
[...42]; // TypeError: 42 is not iterable [...'abc']; // works: ['a', 'b', 'c'] - strings are iterable [...new Set([1, 2, 2])]; // works: [1, 2] - Set is iterable

Rest must be the last parameter:

javascript
function fn(a, ...rest, b) {} // SyntaxError function fn(a, ...rest) {} // correct

Real-World Usage

  • React: <Button {...buttonProps} /> spreads props onto JSX elements
  • Redux reducers: { ...state, count: state.count + 1 } for immutable updates
  • Express middleware: { ...req.query, filter: 'active' } adds query params
  • Deduplication with Set: [...new Set([1, 2, 2, 3])] returns [1, 2, 3]

Follow-up Questions

Q: What does const a = [1]; const b = [...a]; a.push(2); console.log(b); output?
A: [1]. Spread creates a new array, so b is independent of a.

Q: What did developers use before object spread (ES2018)?
A: Object.assign({}, obj1, obj2). Babel still transpiles object spread to Object.assign for older build targets, so the output is identical.

Q: What does rest return when no extra arguments are passed?
A: An empty array []. This is cleaner than the old arguments object, which was array-like but not a real array.

Q: Can you spread a Set or Map?
A: Yes, both are iterable. [...new Set([1, 2, 2])] gives [1, 2]. Useful for quick deduplication.

Q: Why use rest parameters instead of the arguments object?
A: arguments is array-like but not a real array, so .map() and .reduce() don't work on it directly. Rest gives you an actual array. Arrow functions also have no arguments of their own at all.

Examples

Merging User Settings

javascript
const defaults = { theme: 'light', fontSize: 14, showSidebar: true }; const userPrefs = { fontSize: 18, language: 'en' }; // Later keys override earlier ones const settings = { ...defaults, ...userPrefs }; // { theme: 'light', fontSize: 18, showSidebar: true, language: 'en' }

Order matters. ...userPrefs comes second, so its fontSize overwrites the default. This pattern is standard in config merging and theme overrides.

Variable Argument Logger

javascript
function log(level, ...messages) { const prefix = `[${level.toUpperCase()}]`; console.log(prefix, ...messages); // spread messages back out } log('info', 'User logged in', 'userId:', 42); // [INFO] User logged in userId: 42

Rest collects messages into an array. Then spread passes them back as separate arguments to console.log. Rest in, spread out.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?