Skip to main content

Spread and rest operator

The ... operator is one syntax with two roles: spread unpacks an iterable or an object's properties into separate elements, while rest gathers the remaining elements into a single array or object. The role is decided not by the operator itself but by where it stands: in a literal or a function call it is spread, in function parameters and in destructuring it is rest.

Theory

TL;DR

  • ... in a literal or a call expands values: [...arr], {...obj}, sum(...nums).
  • ... in destructuring or in a parameter list gathers the remainder: [a, ...rest], function fn(...args).
  • Spread makes a shallow copy: nested objects and arrays are copied by reference.
  • Spread over objects merges properties, and a later key overwrites an earlier one.
  • It is the modern replacement for concat, Object.assign and Array.prototype.slice.call(arguments).

Quick example

javascript
const numbers = [1, 2, 3]; const moreNumbers = [0, ...numbers, 4, 5]; // spread: the array is unpacked console.log(moreNumbers); // [0, 1, 2, 3, 4, 5] const [first, ...rest] = moreNumbers; // rest: the remainder is gathered console.log(first); // 0 console.log(rest); // [1, 2, 3, 4, 5]

Spread: unpacking arrays

Spread is used to unpack (expand) an array or an object into separate elements.

javascript
const numbers = [1, 2, 3]; const moreNumbers = [0, ...numbers, 4, 5]; console.log(moreNumbers); // [0, 1, 2, 3, 4, 5]

Here ...numbers expanded the array [1, 2, 3] inside a new array.

Copying arrays:

javascript
const arr1 = [1, 2, 3]; const arr2 = [...arr1]; console.log(arr2); // [1, 2, 3] console.log(arr1 === arr2); // false (a new array)

This is a shallow copy: only the outer array is new.

Merging arrays:

javascript
const a = [1, 2]; const b = [3, 4]; const c = [...a, ...b]; console.log(c); // [1, 2, 3, 4]

Spread in function calls

In a call, spread turns an array into a list of separate arguments.

javascript
function sum(a, b, c) { return a + b + c; } const nums = [1, 2, 3]; console.log(sum(...nums)); // 6

...nums turns [1, 2, 3] into sum(1, 2, 3).

Spread with objects

The same syntax copies own enumerable properties from one object into another.

javascript
const user = { name: 'Alex', age: 25 }; const newUser = { ...user, city: 'Paris' }; console.log(newUser); // { name: 'Alex', age: 25, city: 'Paris' }

When keys collide, the later value overwrites the earlier one:

javascript
const merged = { a: 1, ...{ a: 2, b: 3 } }; console.log(merged); // { a: 2, b: 3 }

Rest: gathering the remainder

Rest works the other way round: it does not unpack, it gathers the elements that are left into an array or an object.

In arrays:

javascript
const [first, ...rest] = [1, 2, 3, 4]; console.log(first); // 1 console.log(rest); // [2, 3, 4]

In objects:

javascript
const user = { name: 'Alex', age: 25, city: 'Paris' }; const { name, ...info } = user; console.log(name); // Alex console.log(info); // { age: 25, city: 'Paris' }

In functions:

javascript
function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0); } console.log(sum(1, 2, 3, 4)); // 10

...numbers gathers every argument into a real array, unlike the array-like arguments.

Spread vs rest and what it replaced

FeatureSpreadRest
What it doesUnpacksGathers
Where it is usedIn a function call, when building an array or objectIn a function declaration or in destructuring
Example[...arr], {...obj}, fn(...args)function fn(...args), [a, ...rest]

Before and after:

Before ES6With ES6
arr1.concat(arr2)[...arr1, ...arr2]
Object.assign({}, obj1, obj2){...obj1, ...obj2}
Array.prototype.slice.call(arguments)(...args) => {}

Summary of the behaviour:

ContextBehaviour of ...Example
Spread (unpacking)Expands an array or object[...arr], {...obj}, sum(...nums)
Rest (remainder)Gathers the remaining elements[a, ...rest], function fn(...args)

Common mistakes

  1. Treating spread as a deep copy. Spread and rest are shallow: nested objects and arrays are copied by reference, so changing a nested field in the copy is visible in the original.

    javascript
    const obj = { nested: { a: 1 } }; const copy = { ...obj }; copy.nested.a = 2; console.log(obj.nested.a); // 2, not 1

    A deep copy needs structuredClone(obj) or a manual walk of the structure.

  2. Using ... outside a destructuring context. const ...rest = arr; is a syntax error; the correct form is const [a, ...rest] = arr;.

  3. Putting rest anywhere but last. function fn(...args, last) {} does not parse: a rest parameter must be the final one, because it swallows everything that is left.

  4. Forgetting key order in object spread. { ...defaults, ...options } and { ...options, ...defaults } give different results, because the rightmost object wins.

  5. Confusing ...args with arguments. arguments is an array-like object that does not exist in arrow functions; a rest parameter always gives a real array with map, filter and reduce.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.