JavaScript · Intermediate

Spread and Rest in JavaScript

9 min readUpdated September 24, 2026Every example verified

In short: The three dots ... have two opposite jobs in JavaScript. As spread they expand an iterable or an object into individual elements, arguments or properties: [...a, ...b], f(...args), { ...defaults, ...overrides }. As rest they collect what is left into an array or object: function f(first, ...others) or const [head, ...tail] = list.

Two directions, one symbol

The same three characters do opposite things depending on where they appear, and telling the two apart is the whole lesson. Spread appears where JavaScript expects several separate things: the argument list of a call, the elements of an array literal, the properties of an object literal. It takes one value and unpacks it into those slots. Rest appears where variables are being created: a parameter list or a destructuring pattern. It gathers everything that was not matched into a single array (or, in an object pattern, a single object).

Spread into an array literal, [...morning, "Lead", ...afternoon], builds a new array from the pieces, which is how you concatenate, insert in the middle or copy. Spread into a call, Math.max(...temps), passes the elements of an array as separate arguments, which is what a function such as Math.max wants. Array spread works on any iterable, so a string spreads into its characters and a Set or Map spreads into its entries; that is a convenient way to turn either into an array.

Object spread, { ...defaults, ...userPrefs }, copies the own enumerable properties of each source into a new object. Later sources win, so the properties from userPrefs override the ones from defaults. Adding a property to the copy, { ...settings, version: 3 }, leaves the original untouched, which is why object spread is the standard way to update data without mutation. Array spread and rest parameters are ES2015; object spread and object rest arrived in ES2018.

The copies are shallow. Spread copies one level: the new array or object holds the same nested arrays and objects as the source, not duplicates of them. Changing copy.address.city changes the original's address too, because both hold one shared address object. For a deep copy use structuredClone (available in Node.js 17 and modern browsers) or copy the nested levels explicitly.

A rest parameter must be the last parameter and collects every remaining argument into a real array, so a function can accept any number of values and still use .length, .map and .reduce on them. It replaces the old arguments object, which is array-like but not an array and does not exist in arrow functions. A rest element in a destructuring pattern, const [winner, ...others] = results, must also be last; in an object pattern, const { id, ...details } = record, it gathers the properties not named elsewhere in the pattern into a new object. That last form is a clean way to drop one property from an object without touching the original.

Syntax

 JavaScript · syntax
// spread: expand into separate items
const merged = [...listA, ...listB];
const copy = [...list];
const chars = [..."text"];
Math.max(...numbers);
const settings = { ...defaults, ...overrides, extra: 1 };

// rest: collect what is left
function f(first, ...others) { /* others is an array */ }
const [head, ...tail] = list;
const { id, ...withoutId } = record;

Spread needs an iterable when used in an array literal or a call, and any object when used in an object literal. A rest parameter or rest element must come last.

Spread in array literals and calls

Two shift lists are merged with a name in between, an array is copied so the original stays intact, and Math.max receives an array's elements as arguments.

 JavaScript
const morning = ["Ana", "Bo"];
const afternoon = ["Cy", "Dee", "Eli"];
const fullDay = [...morning, "Floor lead", ...afternoon];
console.log(fullDay);
console.log(fullDay.length);

const temps = readline().split(" ").map(Number);
console.log(Math.max(...temps), Math.min(...temps));

const copy = [...morning];
copy.push("Zed");
console.log(morning, copy);
console.log([..."hey"]);

Input given to the program: 18 24 21 19

Output

[ 'Ana', 'Bo', 'Floor lead', 'Cy', 'Dee', 'Eli' ]
6
24 18
[ 'Ana', 'Bo' ] [ 'Ana', 'Bo', 'Zed' ]
[ 'h', 'e', 'y' ]

[...morning, "Floor lead", ...afternoon] lays the elements of both arrays into one new array with a plain value in the middle. Math.max(...temps) is the same as Math.max(18, 24, 21, 19); passing the array itself would give NaN. Pushing onto copy does not touch morning, because spread built a new array. A string is iterable, so spreading it produces its characters.

Rest parameters and rest elements

A function accepts a customer name plus any number of items, another sums however many prices it is given, and destructuring patterns gather leftovers from an array and an object.

 JavaScript
function logOrder(customer, ...items) {
  const list = items.length ? items.join(", ") : "nothing";
  console.log(`${customer} ordered ${items.length} item(s): ${list}`);
}
logOrder("Priya", "soup", "bread", "tea");
logOrder("Marco");

function total(...prices) {
  return prices.reduce((sum, p) => sum + p, 0);
}
console.log(total(4.5, 3, 2.25));
console.log(total());

const [winner, runnerUp, ...others] = ["Kite", "Otter", "Pine", "Quill"];
console.log(winner, runnerUp, others);

const { id, ...details } = { id: 7, size: "M", colour: "teal" };
console.log(id, details);

Output

Priya ordered 3 item(s): soup, bread, tea
Marco ordered 0 item(s): nothing
9.75
0
Kite Otter [ 'Pine', 'Quill' ]
7 { size: 'M', colour: 'teal' }

items is a genuine array: when Marco passes no items it is an empty array, not undefined, so .length and .join are safe. total() with no arguments reduces an empty array to the initial value 0. In the array pattern, ...others takes every element after the first two; in the object pattern, ...details takes every property except id, which gives a new object with id removed while the original is unchanged.

Object spread: merging, overriding and the shallow-copy trap

User preferences override defaults, a property is added without mutating the source, and the last block shows what a shallow copy does and does not protect.

 JavaScript
const defaults = { theme: "light", fontSize: 14, sidebar: true };
const userPrefs = { theme: "dark", fontSize: 16 };
const settings = { ...defaults, ...userPrefs };
console.log(settings);

const withVersion = { ...settings, version: 3 };
console.log(withVersion.version, settings.version);

const profile = { name: "Ida", address: { city: "Tromso" } };
const shallow = { ...profile };
shallow.name = "Ida M.";
shallow.address.city = "Bergen";
console.log(profile.name, profile.address.city);

Output

{ theme: 'dark', fontSize: 16, sidebar: true }
3 undefined
Ida Bergen

Properties are copied left to right, so userPrefs.theme overwrites defaults.theme while sidebar, which only the defaults define, survives. withVersion gets a version property; settings does not, which is the non-mutating update pattern. The last line is the trap: changing shallow.name left profile.name alone because strings are copied by value, but shallow.address and profile.address are the same object, so changing the city through one changes it for both.

Spread or rest? Decide by position

Where the dots appearMeaningExample
Array literalspread: expand an iterable[...a, ...b]
Function call argumentsspread: pass elements as argumentssum(...values)
Object literalspread: copy own enumerable properties{ ...base, x: 1 }
Last parameter of a functionrest: gather remaining arguments into an arrayfunction f(a, ...rest)
Last element of an array patternrest: gather remaining elementsconst [x, ...ys] = list
Last name of an object patternrest: gather remaining propertiesconst { a, ...others } = obj

Common mistakes

  • Spreading a plain object into an array or a call

    Why it goes wrong: Array spread and call spread need an iterable. A plain object is not iterable, so [...{ a: 1 }] and f(...obj) throw a TypeError. Numbers are not iterable either.

    Fix: Spread the part that is iterable: [...Object.keys(obj)], [...Object.entries(obj)], or use object spread inside braces.

     JavaScript · fix
    const pairs = [...Object.entries({ a: 1, b: 2 })];   // [ [ 'a', 1 ], [ 'b', 2 ] ]
  • Treating a spread copy as a deep copy

    Why it goes wrong: { ...state } and [...list] copy one level only. Nested arrays and objects are shared with the original, so a change through the copy is visible through the original.

    Fix: Copy the nested level too, { ...state, address: { ...state.address } }, or use structuredClone(state) when a full deep copy is what you mean.

  • Putting the rest parameter anywhere but last

    Why it goes wrong: function f(...items, last) is a syntax error. Rest has to be last because it takes everything that remains; nothing can come after it.

    Fix: Move the rest parameter to the end and, if the final argument matters, take it off the array inside the function.

  • Reaching for the arguments object

    Why it goes wrong: arguments is array-like, has no map or reduce, and is not available in arrow functions. Code that uses it usually converts it to an array first anyway.

    Fix: Declare a rest parameter: function f(...args) gives a real array from the start.

Where you use this

Spread earns its place wherever data should be updated without being mutated. Application state in front-end frameworks is usually treated as read-only: to change one field you build a new object with { ...state, count: state.count + 1 } and hand that back, so change detection can compare references and older snapshots stay valid. The same applies to arrays: [...items, newItem] adds to the end and items.filter(...) removes, neither touching the original.

Rest parameters are the natural fit for helpers that accept a varying number of values, such as a log(level, ...parts) function that joins whatever it is given, or a pick(obj, ...keys) that copies only the named keys. Rest in an object pattern is the shortest correct way to strip one property, for example removing a password field before sending a user record to a client.

 JavaScript · in practice
function update(state, changes) {
  return { ...state, ...changes };
}
const { password, ...publicUser } = user;

Key points

  • Spread expands; rest collects. Position decides which one ... means.
  • Array and call spread need an iterable; object spread copies own enumerable properties, later sources overriding earlier ones.
  • Spread copies one level only. Nested objects are shared, so use structuredClone or copy the inner level for a deep copy.
  • A rest parameter is a real array and must be the last parameter; prefer it to the arguments object.
  • A rest element in a pattern must be last; the object form makes a copy without the named properties.
  • Array spread and rest parameters are ES2015; object spread and object rest are ES2018.

Try it yourself

The function averages exactly three numbers. Change it to take any number of values with a rest parameter and call it by spreading the array read from input, so the program prints the average of all four numbers.

Your program
function average(a, b, c) {
  return (a + b + c) / 3;
}

const nums = readline().split(" ").map(Number);
console.log(average(nums[0], nums[1], nums[2]));
Input the program receives: 8 9 10 13
Expected output: 10

Practise this

Exercises for this lesson are in the JavaScript practice set.

Open the JavaScript playground

Frequently asked questions

What is the difference between spread and rest in JavaScript?

They use the same ... but point in opposite directions. Spread takes one array, iterable or object and expands it into separate elements, arguments or properties, so it appears inside array literals, call argument lists and object literals. Rest gathers several separate things into one array or object, so it appears in parameter lists and destructuring patterns, and it must be the last item there.

Does spread make a deep copy?

No. [...arr] and { ...obj } create a new outer array or object, but every nested array or object inside it is the same one the original holds. If you need the nested parts duplicated as well, use structuredClone(value) (Node.js 17+, modern browsers) or spread each nested level yourself.

Can I spread a Map or a Set into an array?

Yes, because both are iterable. [...set] gives an array of the set's values, and [...map] gives an array of [key, value] pairs; [...map.keys()] and [...map.values()] give just the keys or just the values. This is the usual way to convert either into an array for sorting or mapping.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with Node.js 22 at build time; runs in your browser in an isolated Web Worker by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.