JavaScript Spread & Rest Table

PatternWhat it doesField note
[...arr]Copy an arrayShallow copy - nested objects still shared
arr1.concat(arr2) vs [...a, ...b]Merge arraysSpread wins on readability, same result
Math.max(...arr)Spread into argumentsWatch the argument limit on huge arrays (~100k)
{...obj}Copy an objectShallow too; nested objects need deep handling
{...defaults, ...overrides}Merge with overrideLater spread wins per key - the options pattern
function f(...args)Rest parametersReal array, unlike the legacy arguments object
const [first, ...rest] = arrRest in destructuringfirst + rest-of-array in one line
const {a, ...others} = objOmit a keyThe idiomatic key-removal without delete
Reference: the MDN spread syntax reference. One syntax, three jobs: spread EXPANDS iterables into slots (calls, arrays, objects), rest COLLECTS the remainder into a variable - same three dots, opposite directions depending on position. The shallow-copy caveat is the deepest: one level is copied, nested objects stay shared between the original and the copy, so mutation leaks through. Bottom line: spread for merges and copies, rest for flexible functions, structuredClone() when a true deep copy is the actual need. Related tools: array methods table, destructuring table (rest's other half), and equality table for why copies compare unequal.

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

  1. Read the dots by position: in a call or literal they spread (expand); in a parameter list or destructuring they rest (collect).
  2. Merge configurations with {...defaults, ...overrides} - later spreads win per key, no loop needed.
  3. 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.

Related tools