JavaScript Spread & Rest Table
| Pattern | What it does | Field note |
|---|---|---|
[...arr] | Copy an array | Shallow copy - nested objects still shared |
arr1.concat(arr2) vs [...a, ...b] | Merge arrays | Spread wins on readability, same result |
Math.max(...arr) | Spread into arguments | Watch the argument limit on huge arrays (~100k) |
{...obj} | Copy an object | Shallow too; nested objects need deep handling |
{...defaults, ...overrides} | Merge with override | Later spread wins per key - the options pattern |
function f(...args) | Rest parameters | Real array, unlike the legacy arguments object |
const [first, ...rest] = arr | Rest in destructuring | first + rest-of-array in one line |
const {a, ...others} = obj | Omit a key | The idiomatic key-removal without delete |
Three dots, two directions: spread EXPANDS an iterable or object into slots (function calls, array literals, object literals), rest COLLECTS the remainder into a variable - same syntax, opposite direction depending on where it appears. The table below is the working eight, from array copy to key omission.
Bottom line: the merge pattern {...defaults, ...overrides} is the options idiom - later spreads win per key, so user settings override defaults cleanly without a loop. Rest parameters replaced the arguments object with a real array, and rest in destructuring is the idiomatic key-omission ({a, ...others} = obj) - no delete, no mutation.
The honest part: every spread copy is SHALLOW. One level deep is copied; nested objects stay shared between the original and the copy, so mutating copy.nested.address also mutates original.nested.address. When true isolation is the need, structuredClone() does a real deep copy - spread is for merges and top-level changes, not for inventing immutability.
How to use
- Read the dots by position: in a call or literal they spread (expand); in a parameter list or destructuring they rest (collect).
- Merge configurations with {...defaults, ...overrides} - later spreads win per key, no loop needed.
- Watch the depth: spread copies are one level deep; use structuredClone() when nested objects must be independent.
Frequently asked questions
What is the difference between spread and rest?
Position decides. In a function CALL or a literal, three dots SPREAD - expanding an array into arguments or elements. In a function PARAMETER list or destructuring pattern, three dots REST - collecting the remaining pieces into an array or object. Same syntax, mirror jobs: spread unpacks, rest packs. Reading code, ask 'is this dots position giving values out or gathering values in?'
How deep does the spread copy go?
One level. [...arr] creates a new array whose elements ARE the original elements - primitives are truly copied, but nested objects and arrays are the same references, so mutations leak through in both directions. For a real deep copy, structuredClone(value) handles nested structures, dates, Maps and Sets natively. The middle ground: spread the top level, then spread the nested fields you actually mutate.
Why use rest parameters instead of the arguments object?
rest (...args) is a REAL array - map, filter and spread all work directly - while the legacy arguments object is array-like (length only, needs Array.from for methods), unavailable in arrow functions, and captures everything including what you named. Rest also reads as part of the signature: function f(first, ...others) documents its arity in the header. New code never writes arguments.
How do I remove a key from an object without deleting it?
Rest in destructuring: const {password, ...safeUser} = user - password is extracted (often to be ignored), and safeUser is a fresh object without it. This beats delete for three reasons: the original user stays untouched, the result is a new object that frameworks detect as changed, and the intent (strip password for the response) reads in one line. delete mutates and deoptimizes engines' hidden classes.