JavaScript Spread vs Rest Operators, Explained
The spread operator (...) expands an iterable into individual elements; the rest operator collects elements back into an array. Same syntax, opposite jobs.
The spread and rest operators both use the same ... syntax, but they do opposite things: spread expands an iterable (an array, string, or object) into its individual elements, while rest collects multiple elements back into a single array. Which one you’re looking at depends entirely on where the ... appears — inside a function call or literal, it’s spread; inside a function’s parameter list or a destructuring pattern, it’s rest.
Spread: expanding a collection
Spread takes something iterable and unpacks it into individual values, wherever a list of values is expected.
In function calls, spread replaces the old Function.prototype.apply trick for passing an array as individual arguments:
const nums = [4, 8, 15];
Math.max(...nums); // same as Math.max(4, 8, 15)
In array literals, spread is the idiomatic way to copy or merge arrays without mutating the originals:
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]
const copy = [...a]; // a shallow copy of a
In object literals, spread copies enumerable own properties, and later keys overwrite earlier ones — a compact alternative to Object.assign:
const defaults = { theme: "dark", timeout: 30 };
const overrides = { timeout: 60 };
const config = { ...defaults, ...overrides }; // { theme: "dark", timeout: 60 }
That merge pattern is common in state updates, where you want a new object rather than mutating an existing one — the same instinct behind destructuring assignment, which spread frequently pairs with.
One caveat worth remembering: spread only produces a shallow copy. Nested objects and arrays are still shared by reference between the original and the copy, so mutating a nested property affects both.
Rest: collecting the remainder
Rest does the inverse: it gathers a variable number of remaining elements into a single array. It shows up in two places.
In function parameters, rest collects any arguments beyond the named ones, replacing the old arguments object with a real array:
function sum(first, ...rest) {
return rest.reduce((total, n) => total + n, first);
}
sum(1, 2, 3, 4); // 10
Unlike arguments, a rest parameter is a genuine Array, so methods like .map(), .filter(), and .reduce() — covered in our guide to map, filter, and reduce — work on it directly without conversion. A rest parameter must also be the last parameter in the list; JavaScript needs to know where the “named” arguments stop and the collection begins.
In destructuring, rest captures whatever properties or elements weren’t explicitly pulled out:
const [first, ...others] = [10, 20, 30];
// first = 10, others = [20, 30]
const { id, ...rest } = { id: 1, name: "Ada", role: "admin" };
// id = 1, rest = { name: "Ada", role: "admin" }
This pattern is especially common for splitting out one or two known fields from an object while forwarding the rest — passing unrecognized props through to a child component, for instance.
Telling them apart
The syntax is identical; the position and direction are what distinguish them.
| Spread | Rest | |
|---|---|---|
| Direction | Expands a collection into elements | Collects elements into a collection |
| Appears in | Function calls, array/object literals | Function parameters, destructuring patterns |
| Typical use | Copying, merging, passing an array as args | Variadic functions, splitting off “the rest” |
| Position rule | Anywhere in a list | Must be last |
A useful mental shortcut: if ... is on the receiving side of an assignment or parameter list, it’s rest. If it’s on the producing side — inside a call, an array literal, or an object literal — it’s spread.
Where this replaced older patterns
Before ES2015 introduced spread and ES2018 extended it to objects, these jobs required more verbose alternatives: Function.prototype.apply() for spreading arrays into calls, Object.assign({}, a, b) for merging objects, .slice(1) combined with arguments for capturing extra parameters, and .concat() for combining arrays. Spread and rest didn’t add new capability so much as give existing patterns a consistent, terse syntax — one of many ergonomics improvements that shaped how modern JavaScript is written, alongside changes like let and const replacing var for variable scoping.
A common gotcha: arrays vs objects vs strings
Spread works on anything iterable, which includes arrays, strings, Map, Set, and NodeLists, but plain objects are not iterable by default. Object spread ({ ...obj }) works through a separate mechanism specific to object literals — it’s not the same iteration protocol as array spread, which is why you can’t use ... to spread an object directly into a function call the way you can an array.
const str = "abc";
console.log([...str]); // ['a', 'b', 'c'] — strings are iterable
function fn(...args) {}
fn(...{ a: 1 }); // TypeError — plain objects aren't iterable
If you need to pass an object’s values as individual arguments, spread Object.values(obj) instead.
The takeaway
Spread and rest share the ... syntax but point in opposite directions: spread unpacks a collection into individual values inside a call or literal, while rest packs individual values back into an array inside a parameter list or destructuring pattern. Once that direction clicks, the rest is mostly muscle memory — spread for copying and merging arrays or objects, rest for variadic functions and pulling the “remaining” fields out of a destructure. Both replaced older, more verbose patterns without adding new runtime capability, which is exactly why they read so naturally once you’re used to them.
Tagged
Keep reading
Takina · · 5 min read Promise.all() vs allSettled() vs race() Compared
Promise.all() fails fast, allSettled() waits for every result, and race() returns whichever promise finishes first — how to choose correctly.
Takina · · 4 min read ResizeObserver API Explained: Watching Element Size
The ResizeObserver API lets JavaScript watch an element's box size and react without polling or resize-event hacks. How it works and when to use it.
Takina · · 4 min read localStorage vs sessionStorage vs Cookies
localStorage, sessionStorage, and cookies all store data in the browser, but differ in lifetime, size limits, and whether the server can see them.