JavaScriptArray Spread & Rest

Array Spread & Rest

The three little dots ... are two features that look identical and do opposite things. Spread expands an iterable into its individual elements. Rest collects individual elements into an array. Same syntax, different position: spread shows up on the right of =, rest on the left.

Spread — expanding an iterable

Use ...iter in an array literal, function call or new-expression to inline every element of the iterable in place.

JS
const a = [1, 2, 3];

// Inside another array literal
const b = [0, ...a, 4];
// [0, 1, 2, 3, 4]

// As function arguments
Math.max(...a);          // 3
Math.min(1, ...a, 0);    // 0

// In a new-expression
new Date(...[2025, 0, 1]);   // Jan 1, 2025

// Works on any iterable, not just arrays
[..."abc"];              // ["a", "b", "c"]
[...new Set([1, 1, 2])]; // [1, 2]
Cloning and merging

The most common everyday uses for spread are cloning and merging. Both are shallow — nested arrays and objects are still shared.

JS
const original = [1, 2, 3];

// Shallow clone
const copy = [...original];
copy.push(4);
original;          // [1, 2, 3]
copy;              // [1, 2, 3, 4]

// Merge / concat
const a = [1, 2];
const b = [3, 4];
const c = [...a, ...b];        // [1, 2, 3, 4]

// Prepend, append, insert in the middle
const head = [0, ...a];         // [0, 1, 2]
const tail = [...a, 3];         // [1, 2, 3]
const mid  = [a[0], 99, ...a.slice(1)]; // [1, 99, 2]
Spread is shallow
`[...users]` copies the outer array but the *same* user objects are reused. Mutating `copy[0].name = "X"` also changes `original[0].name`. For a deep copy use `structuredClone(arr)`.
Spread vs concat — when each wins

Most of the time spread is preferred — it reads naturally and works with iterables. There are still cases where concat is cleaner:

  • Single-value or array of unknown shape: a.concat(maybe) accepts both a single value and an array; spread would need a runtime check.

  • Very large arrays: concat is implemented in optimised C++ and can be faster than copying via spread for hundreds of thousands of elements.

  • Iterables that are not arrays: spread wins — concat requires arrays (or array-likes).

  • Modern code: prefer spread for readability and consistency with object spread.

JS
const a = [1, 2];
const maybe = Math.random() < 0.5 ? 3 : [3, 4];

a.concat(maybe);       // works for either case
[...a, ...[].concat(maybe)];   // need to normalise first — uglier
Rest — collecting elements

Rest appears in two places: in destructuring patterns and in function parameter lists. Both gather "everything else" into a fresh array.

JS
// In array destructuring
const [first, ...others] = [1, 2, 3, 4];
// first = 1, others = [2, 3, 4]

// In a function signature
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3);       // 6
sum(...[1, 2, 3]);  // 6 — spread the array as arguments

// Rest captures only the trailing arguments
function logCall(prefix, ...args) {
  console.log(prefix, args);
}
logCall("call:", 1, 2, 3);
// call: [ 1, 2, 3 ]
Rest must be last
There is no way to write `function f(...rest, last)`. Rest always sits at the end of the pattern.
Rest parameter vs arguments

Older JavaScript used the magic arguments object inside functions. It is an array-like (no .map, no .filter) and does not exist in arrow functions. Rest parameters are simpler in every respect.

JS
// Old style
function legacy() {
  console.log(arguments);          // not a real array
  return [].slice.call(arguments); // ceremony to use array methods
}

// Modern
function modern(...args) {
  return args;   // real array, ready to map/filter/reduce
}

// In arrow functions, only rest works — arguments is not defined.
const arrow = (...xs) => xs.reduce((a, b) => a + b, 0);
A real example: a tiny logger

JS
function withPrefix(prefix) {
  return (...parts) => console.log(prefix, ...parts);
}

const info  = withPrefix("[info]");
const error = withPrefix("[error]");

info("server started", { port: 3000 });
error("could not parse", "file.json", "line", 42);
[info] server started { port: 3000 }
[error] could not parse file.json line 42
Quick recap
  • Spread expands an iterable. Used on the right-hand side: array literals, function calls, new.

  • Rest collects elements into an array. Used on the left-hand side: destructuring patterns and parameter lists.

  • Both make a shallow copy of what they touch.

  • Spread is the modern way to clone and merge arrays; rest replaces the old arguments object.

  • Same three dots, opposite directions — context tells you which one you are looking at.

One-liner shortcuts
Deduplicate: `[...new Set(arr)]`. Turn an iterable into an array: `[...iter]`. Concatenate any iterables: `[...a, ...b, ...c]`.