Spread vs Rest Operators in JavaScript
Expand what you have, collect what you need — mastering spread and rest operators in JavaScript
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.
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 |
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.



