JavaScript · Intermediate
Spread and Rest in JavaScript
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
// 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.
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.
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.
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 BergenProperties 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 appear | Meaning | Example |
|---|---|---|
| Array literal | spread: expand an iterable | [...a, ...b] |
| Function call arguments | spread: pass elements as arguments | sum(...values) |
| Object literal | spread: copy own enumerable properties | { ...base, x: 1 } |
| Last parameter of a function | rest: gather remaining arguments into an array | function f(a, ...rest) |
| Last element of an array pattern | rest: gather remaining elements | const [x, ...ys] = list |
| Last name of an object pattern | rest: gather remaining properties | const { 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 }]andf(...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 · fixconst 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 usestructuredClone(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:
argumentsis array-like, has nomaporreduce, 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.
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
structuredCloneor 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
argumentsobject. - 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.
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]));8 9 10 1310function average(...values) {
let sum = 0;
for (const v of values) sum += v;
return sum / values.length;
}
const nums = readline().split(" ").map(Number);
console.log(average(...nums));Practise this
Exercises for this lesson are in the JavaScript practice set.
Open the JavaScript playground
Related lessons
- Destructuring in JavaScriptHow JavaScript destructuring unpacks arrays and objects into variables, with defaults, renaming, nested patterns, swaps and function parameters.9 min
- JavaScript Arrays: Creating, Indexing and Changing ListsHow JavaScript arrays store ordered lists: indexing, length, push and pop, slice versus splice, sorting numbers correctly, and map, filter and reduce.10 min
- JavaScript Objects: Properties, Methods and NestingHow JavaScript objects group data as key-value properties: dot and bracket access, adding and deleting, methods with this, nesting and reference behaviour.10 min
- JavaScript Functions: Declarations, Arrows and Return ValuesHow to define and call JavaScript functions with declarations, expressions and arrow functions; parameters, default values, return and local scope.10 min
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.
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.