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

📚 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:

🎒 Real-World Analogy: Think of a backpack full of items:

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, eggs

5️⃣ 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

Quick Reference — Spread & Rest

GoalSyntax
Copy arrayconst c = [...arr];
Merge arrays[...a, ...b]
Clone/override object{ ...obj, key: val }
Spread into callMath.max(...arr)
Rest parameterfunction f(...args) {}
Rest in destructuringconst [first, ...rest] = arr;

Lesson Complete — Spread & Rest Operators!

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

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.

Related lessons