Spread & Rest Operators
Reviewed & published by Brayan K
Three little dots — ... — do two opposite jobs depending on where you write them. As spread they pour a collection out into its pieces; as rest they scoop loose pieces back into one collection.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
They power immutable updates in React/Redux, neat array merges, and flexible function signatures. Master both halves of the ... and your code gets noticeably cleaner.
What You'll Learn in This Lesson
- Spread arrays to copy and merge
- Spread objects to clone and override
- Spread into function calls
- Gather arguments with rest parameters
- Collect leftovers in destructuring
- Why spread copies are shallow
📚 Prerequisites: Finish Destructuring first — rest in destructuring builds directly on it. Comfort with arrays, objects, and functions is assumed.
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a .html file with <script> tags and open it in your browser
🎒 Real-World Analogy: Think of a backpack full of items:
- • Spread = tip the backpack out and lay every item on the table individually
- • Rest = sweep a pile of loose items back into one new backpack
- • Spreading lets you combine two backpacks into a fresh one
- • Resting lets a function accept "however many items you give me"
1️⃣ Spread: Copy & Merge Arrays
Put ...arr inside a new array literal and its elements are dropped in place. This gives you a quick copy (a new array, not a reference) and an easy way to merge — no concat needed. This pattern is the backbone of immutable state updates.
const base = [1, 2, 3];
// Copy: a NEW array with the same items
const copy = [...base];
console.log(copy); // ✅ Expected output: [ 1, 2, 3 ]
console.log(copy === base); // ✅ Expected output: false (different array)
// Merge two arrays and insert extras anywhere
const more = [4, 5];
const merged = [0, ...base, ...more, 6];
console.log(merged); // ✅ Expected output: [ 0, 1, 2, 3, 4, 5, 6 ]2️⃣ Spread: Clone & Override Objects
The same trick works for objects. Spread an object into a new { } to clone it, then add or replace keys. Because later keys win, this is the standard way to apply overrides on top of defaults — and to update one field without mutating the original.
const user = { name: "Ada", role: "guest", active: true };
// Update one field WITHOUT mutating the original
const promoted = { ...user, role: "admin" };
console.log(user.role); // ✅ Expected output: guest (original untouched)
console.log(promoted.role); // ✅ Expected output: admin
// Defaults then overrides: last spread wins
const defaults = { theme: "light", fontSize: 14 };
const settings = { ...defaults, theme: "dark" };
console.log(settings); // ✅ Expected output: { theme: 'dark', fontSize: 14 }3️⃣ Spread Into Function Calls
When a function wants separate arguments but your values live in an array, spread bridges the gap. This is the modern, readable replacement for the old fn.apply(null, arr) trick you may have seen.
const scores = [88, 95, 73, 100, 64];
// Math.max wants max(a, b, c...) — spread the array into arguments
console.log(Math.max(...scores)); // ✅ Expected output: 100
console.log(Math.min(...scores)); // ✅ Expected output: 64
// Spread a string into characters (strings are iterable)
console.log([..."hello"]); // ✅ Expected output: [ 'h', 'e', 'l', 'l', 'o' ]🎯 Your Turn #1 — Merge Two Lists
Combine both arrays into one using spread.
const fruits = ["apple", "pear"];
const veg = ["kale", "corn"];
// 👉 replace ___ so both arrays are spread into one
const groceries = [___fruits, ___veg];
console.log(groceries); // ✅ Expected output: [ 'apple', 'pear', 'kale', 'corn' ]4️⃣ Rest Parameters: "However Many You Give Me"
In a function's parameter list, ...args gathers every remaining argument into a real array. Unlike the old arguments object, you can use map/filter/reduce on it directly. The rest parameter must be last.
// sum any number of values
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3)); // ✅ Expected output: 6
console.log(sum(10, 20, 30, 40)); // ✅ Expected output: 100
// Mix fixed and rest parameters (rest must come last)
function tag(label, ...items) {
return label + ": " + items.join(", ");
}
console.log(tag("Cart", "milk", "bread", "eggs")); // ✅ Expected output: Cart: milk, bread, eggs5️⃣ Rest in Destructuring
Rest also works on the left side, inside a destructuring pattern. Pull off the first item (or a couple of named keys) and gather everything else into a new array or object — perfect for "take the head, keep the tail" or "remove one field" operations.
// Arrays: first item + the rest
const [head, ...tail] = [10, 20, 30, 40];
console.log(head); // ✅ Expected output: 10
console.log(tail); // ✅ Expected output: [ 20, 30, 40 ]
// Objects: remove a key by gathering the rest
const account = { id: 1, password: "secret", name: "Ivy" };
const { password, ...safe } = account;
console.log(safe); // ✅ Expected output: { id: 1, name: 'Ivy' } (password removed)6️⃣ Important: Spread Copies Are Shallow
Spread only copies the top level. Nested objects and arrays are still shared by reference, so mutating them in the "copy" silently changes the original. For a fully independent copy of nested data, reach for structuredClone().
const original = { name: "Lab", tags: ["a", "b"] };
const shallow = { ...original };
// Mutating the NESTED array affects BOTH (shared reference)
shallow.tags.push("c");
console.log(original.tags); // ✅ Expected output: [ 'a', 'b', 'c' ] (oops!)
// structuredClone makes a fully independent deep copy
const deep = structuredClone(original);
deep.tags.push("d");
console.log(original.tags); // ✅ Expected output: [ 'a', 'b', 'c' ] (unaffected)🎯 Your Turn #2 — Collect the Rest
Write a rest parameter that collects all numbers, then return how many there are.
// 👉 replace ___ so 'values' gathers every argument into an array
function countArgs(...___) {
return values.length;
}
console.log(countArgs(1, 2, 3)); // ✅ Expected output: 3
console.log(countArgs("a", "b", "c", "d")); // ✅ Expected output: 4🧩 Reorder Challenge
These lines are scrambled. Reorder them so it logs the merged, overridden object.
Why: both source objects must exist before you spread them, and overrides comes last so its role wins.
🧠 Quick Recall
Predict the output before revealing the answer.
{ a: 2 } — the last spread wins, so the second a overwrites the first.
[ 2, 3 ] — a takes the first item; rest gathers everything else into an array.
2 — rest collects both arguments into the array n, whose length is 2.
Common Errors & Fixes
- 1️⃣ Rest parameter not last
- 2️⃣ Expecting a deep copy from spread
- 3️⃣ Spreading a non-iterable into an array
- 4️⃣ Wrong order when overriding
Quick Reference — Spread & Rest
| Goal | Syntax |
|---|---|
| Copy array | const c = [...arr]; |
| Merge arrays | [...a, ...b] |
| Clone/override object | { ...obj, key: val } |
| Spread into call | Math.max(...arr) |
| Rest parameter | function f(...args) {} |
| Rest in destructuring | const [first, ...rest] = arr; |
Lesson Complete — Spread & Rest Operators!
- ✅ Spread pours a collection out: copy, merge, override, pass as arguments
- ✅ Rest gathers leftovers: variadic parameters and "the tail" in destructuring
- ✅ Same ... — spread on the right, rest on the left
- ✅ Spread copies are shallow; use structuredClone() for deep copies
Practice quiz
Where does the ... act as SPREAD rather than rest?
- In a function's parameter list
- On the left of = in a destructuring pattern
- Inside an array/object literal or function call
- It is always rest
Answer: Inside an array/object literal or function call. On the right side (inside literals or calls) the dots spread a collection out into pieces.
What does { ...{ a: 1 }, ...{ a: 2 } } evaluate to?
- { a: 2 }
- { a: 1 }
- { a: 1, a: 2 }
- { a: 3 }
Answer: { a: 2 }. When keys collide in a merge, the last spread wins, so a becomes 2.
Is a spread copy deep or shallow?
- Deep — fully independent
- It depends on the array length
- It throws for nested data
- Shallow — nested objects are still shared by reference
Answer: Shallow — nested objects are still shared by reference. Spread copies only the top level; nested objects/arrays remain shared, so use structuredClone for a deep copy.
What does Math.max(...[88, 95, 100, 64]) return?
- 88
- 100
- 95
- 64
Answer: 100. Spread turns the array into separate arguments, so Math.max receives 88, 95, 100, 64 and returns 100.
What does [..."hello"] produce?
- ["h", "e", "l", "l", "o"]
- ["hello"]
- [104, 101, ...]
- An error — strings are not iterable
Answer: ["h", "e", "l", "l", "o"]. Strings are iterable, so spreading yields an array of individual characters.
Given const [head, ...tail] = [10, 20, 30, 40], what is tail?
- 10
- [10]
- [20, 30, 40]
- [10, 20, 30, 40]
Answer: [20, 30, 40]. head takes the first element; rest gathers the remaining elements into a new array.
Why must a rest parameter come last? function f(...nums, last) is...
- Valid
- A SyntaxError
- Slower
- Equivalent to (last, ...nums)
Answer: A SyntaxError. A rest parameter must be the final parameter; otherwise it is a SyntaxError.
What advantage do rest parameters have over the legacy arguments object?
- They are array-like only
- They work only in arrow functions
- They are slower but clearer
- They give you a real array with map/filter/reduce
Answer: They give you a real array with map/filter/reduce. Rest parameters produce a real array, so array methods work directly; arguments is only array-like.
Given const account = { id: 1, password: "x", name: "Ivy" } and const { password, ...safe } = account, what is safe?
- { password: "x" }
- { id: 1, name: "Ivy" }
- { id: 1, password: "x", name: "Ivy" }
- undefined
Answer: { id: 1, name: "Ivy" }. Pulling off password and gathering the rest removes that key, leaving { id: 1, name: 'Ivy' }.
Which produces the correct override of defaults with theme: "dark"?
- { theme: "dark", ...defaults }
- { ...defaults, ..."dark" }
- { ...defaults, theme: "dark" }
- { defaults, theme: "dark" }
Answer: { ...defaults, theme: "dark" }. Spread defaults first, then your override last so its key wins.
Continue this course
- Previous: Destructuring Assignment
- Next: map, filter & reduce — The three array methods every JS developer must master
- Quick reference: JavaScript cheat sheet › Arrays
Frequently asked questions
Spread and rest use the same ... symbol — how do I tell them apart?
Look at which side of the equals sign they're on. On the RIGHT (or inside a function call / array / object literal) the dots SPREAD an existing collection out into pieces. On the LEFT (in a parameter list or destructuring pattern) they GATHER leftover pieces into a new array or object. Same syntax, opposite jobs.
Does spread make a deep copy?
No — spread makes a shallow copy. The top-level array or object is new, but any nested objects are still shared by reference. Mutating a nested object in the copy also changes the original. For a true deep copy use structuredClone(value) in modern environments.
When two objects are merged with spread, which value wins?
The last one. In { ...a, ...b } any keys in b overwrite the same keys from a. This is exactly how you apply overrides or defaults: spread your defaults first, then spread the user's options on top.
Why use rest parameters instead of the old arguments object?
Rest parameters give you a real array, so you can call map, filter, and reduce on it directly. The legacy arguments object is only array-LIKE and doesn't exist inside arrow functions at all. Rest is clearer, works everywhere, and shows up right in the function signature.
Can I spread a string?
Yes. Strings are iterable, so [...'abc'] gives ['a', 'b', 'c']. Spread works on anything iterable: arrays, strings, Sets, Maps, and NodeLists, which makes it a quick way to convert them into plain arrays.