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.
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.
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 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:
concatis 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 —
concatrequires arrays (or array-likes).Modern code: prefer spread for readability and consistency with object spread.
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.
// 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 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.
// 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
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 42Quick 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
argumentsobject.Same three dots, opposite directions — context tells you which one you are looking at.