Spread & rest
Learn how JavaScript’s three dots expand arrays, calls, and objects, collect leftover values with rest, and make safe shallow copies and immutable updates.
- 01Tell spread from restLook at where
...stands and say whether it lays items out (spread) or gathers leftovers (rest). - 02Guess the result of a mergeSay what order spread puts items in, and which value wins when two objects have the same property.
- 03Copy without surprisesKnow that spread copies only the outer level, copy inner levels when you need to, and know when
structuredCloneis the better tool.
Same dots, two directions
JavaScript uses three dots, ..., for two opposite jobs. Where the dots stand decides which job they do:
- Spread takes the items out of a list and lays them out one by one.
- Rest gathers the items that are left over into one new list.
You already met rest parameters and spread in function calls in the Default & rest parameters lesson. This lesson uses the dots in more places: arrays, function calls, objects, and destructuring. It also shows which value wins when two objects are merged, why a spread copy can still share data, and the mistakes people make most often.
Spread takes one value apart into many separate items. Rest gathers many leftover items into one value.
const items = ["tea", "toast"];
const menu = [...items, "fruit"];
const [first, ...leftovers] = menu;
console.log(menu.join(" / ") + " | " + first + " | " + leftovers.join(" / "));Line 1 makes a small array, items. Line 2 uses spread while it builds a new array, menu. The two items from items are laid out inside menu, and "fruit" goes after them.
Line 3 uses rest while it reads menu. The variable first gets the first item. The variable leftovers gathers all the other items into a new array. Line 4 prints tea / toast / fruit | tea | toast / fruit.
Picture a bag of marbles. Spread is like pouring the bag out, so every marble sits on its own on the table. Rest is like sweeping the leftover marbles back into one bag. The dots are the same; the direction is opposite.
- In real life: Pour the bag out onto the table
- In JavaScript: Spread:
...baglays out each item - In real life: Each marble now sits on its own
- In JavaScript: Array items, function arguments, or object properties appear one by one
- In real life: Sweep the leftover marbles back into one bag
- In JavaScript: Rest:
...leftoversgathers what was not already taken
The table lists every place the dots can stand. Read a row from left to right: where the dots are, which job they do, what happens, and a short example.
| Where are the dots? | Which job? | What happens? | Short example |
|---|---|---|---|
| In the square brackets of a new array. | Spread | The items are laid out in the new array. | [0, ...items] |
| In the round brackets of a function call. | Spread | Each item becomes its own argument. | fn(...args) |
| In the curly brackets of a new object. | Spread | The object's own properties are copied in. | { ...a, ...b } |
In an array pattern, left of =. | Rest | The items that are left go into a new array. | const [x, ...xs] = list |
In an object pattern, left of =. | Rest | The properties that are left go into a new object. | const { id, ...rest } = obj |
| In a function's parameter list. | Rest | The arguments that are left go into a new array. | function f(...args) {} |
Try the first lab now. Each card has ... in it. Decide whether those dots lay items out (spread) or gather leftovers (rest).
const all = [0, ...numbers];Math.max(...scores)const merged = { ...defaults, ...user };const [first, ...others] = list;const { password, ...safeUser } = user;function sum(...numbers) { }
Sort each card: do the dots lay items out, or gather the leftovers?
Spread in arrays & calls
STEP THROUGHSpread works in two places with lists:
- Inside the square brackets of a new array, spread puts the items of another array at that exact spot. The old array does not change.
- Inside the round brackets of a function call, spread hands each item to the function as its own argument. An argument is a value you pass into a function.
In a new array
["intro", ...front, "quiz"] makes a new array. The old front array stays the same.
In a function call
Math.max(...scores) gives Math.max each score as its own argument. It does not pass the whole array as one argument.
const morning = ["tea"];
const breakfast = ["water", ...morning, "toast"];
console.log(breakfast.join(" + "));Line 1 makes an array with one drink in it. Line 2 makes a new array. It puts the items of morning between two fixed values, so line 3 prints water + tea + toast. The morning array still has its one item. Spread fills the new array; it does not change the old one.
...morning takes the items out of the morning array and puts them at the spot of the dots. The new array gets the item "tea", not the whole array. morning does not change.const word = "tea";
const letters = [...word];
console.log(letters.join("-"));Spread in an array works on any iterable. An iterable is a value that JavaScript can walk through one item at a time. Arrays are iterable. Strings are iterable too: each character is one item. So line 2 makes an array of three letters, and line 3 prints t-e-a.
A plain object, like { theme: "dark" }, is not iterable. To copy an object's properties, use object spread, which comes in the next section.
Now step through a longer example. It spreads two arrays into a new one, then spreads part of that array into a function call. After that, switch line 3 and run it again. Notice that spread keeps the items in the same order. It does not sort them, and it does not change the old arrays.
Guess the order of the items in lesson, then step. Switch line 3 and run again: the old arrays stay the same, but their items land in different spots.
script
const back = ["quiz"];const lesson = ["intro", ...front, "lab", ...back];console.log(lesson.join(" -> "));function announce(first, second, third) { console.log(first + " | " + second + " | " + third);}announce(...lesson.slice(0, 3));The Slicing & splicing lesson showed concat and spread for copying arrays. Spread is a good choice when you want to see at a glance where each item goes.
Spread in objects
INTERACTIVEInside the curly brackets of a new object, spread copies properties from another object into the new one. It copies the object's own enumerable properties:
- Own means the property is stored on the object itself, not borrowed from another object.
- Enumerable means the property shows up when you list the object's keys.
- The properties you write in an object literal, like
{ theme: "dark" }, are both.
Spread copies from left to right. If two objects have a property with the same name, the one copied later replaces the one copied earlier.
Think of each object as a shopping list. You start with the old list, then read the newer one. If both lists mention theme, you follow the newer note.
- In real life: The first shopping list
- In JavaScript:
defaults, copied first - In real life: A newer shopping list
- In JavaScript:
userSettings, copied later - In real life: The same item is on both lists
- In JavaScript: The same property name in both objects
- In real life: You follow the newer note
- In JavaScript: The later property wins
const defaults = { theme: "light", fontSize: 14 };
const user = { theme: "dark" };
const settings = { ...defaults, ...user };
console.log(settings.theme + " / " + settings.fontSize);Line 1 sets two default values. Line 2 sets only a theme. Line 3 copies defaults first and user second. Both have a theme, so the later one, "dark", wins. Only defaults has fontSize, so it stays 14. Line 4 prints dark / 14.
defaults first and user second. Both have a theme, so the later one, "dark", wins. Only defaults has fontSize, so it stays 14.Step through the same idea below. Then switch the order on line 3 and watch the winner change.
Switch the order on line 3 and step again. The same two objects give a different theme, because the object copied later wins.
script
const userSettings = { theme: "dark" };const merged = { ...defaults, ...userSettings };console.log(merged.theme + " / " + merged.pageSize);This is why { ...user, name: "X" } and { name: "X", ...user } are not the same. In the first one, name is always "X", because it comes last. In the second one, user.name comes last, so it replaces "X" if user has a name.
The next block tests some unusual cases. It uses two new tools:
Object.create(parent)makes an empty object that borrows properties fromparent. Borrowed properties are not the object's own.Object.definePropertyadds one property and lets you choose its settings, such asenumerable: false. A getter is a property whose value comes from a function. The function runs each time the property is read.
const safe = { ...null, ...undefined };const letters = { ..."JS" };const source = Object.create({ inherited: "skip" });Object.defineProperty(source, "hidden", { value: "skip", enumerable: false });Object.defineProperty(source, "label", { enumerable: true, get() { return "Ada"; },});const copy = { ...source };console.log(JSON.stringify(safe));console.log(JSON.stringify(letters));console.log(JSON.stringify(copy));console.log([...null]);The first three logs print {}, {"0":"J","1":"S"}, and {"label":"Ada"}:
- Line 1: object spread skips
nullandundefined, sosafeis an empty object. - Line 2: a string's characters are copied as keys
0and1. - Line 9: the borrowed property and the hidden property are skipped. The getter runs, and its result,
"Ada", is copied as a plain value.
The last line throws a TypeError. Spread in an array needs an iterable, and null is not iterable.
Object spread copies own enumerable properties only. It skips borrowed properties and hidden (non-enumerable) properties. If a copied property is a getter, the getter runs, and the new object gets the plain value it returned.
The result is always a plain object. So the methods that an object borrows from its class are not copied. Use spread to copy data, not to copy a class object with its methods.
Rest in destructuring
STEP THROUGHDestructuring takes values out of an array or object and puts them into variables. The left side, like [next, ...waiting], is called the pattern. Rest in a pattern gathers everything that the pattern did not already take. This is the opposite direction from spread.
const queue = ["Asha", "Ben", "Chen"];
const [next, ...waiting] = queue;
console.log(next + " | " + waiting.join(", "));Line 1 makes a queue of three names. Line 2 puts "Asha" into next. Then ...waiting gathers the names that are left into a new array. Line 3 prints Asha | Ben, Chen. Rest makes a new array; it does not remove anything from queue.
next takes the first item, "Asha". Then ...waiting gathers every item that is left into one new array. queue itself does not change.const queue = ["tea"];
const [next, ...waiting] = queue;
console.log(next + " / " + waiting.length);This queue has only one item, and it goes into next. No items are left over. Rest still makes an array, but the array is empty. So line 3 prints tea / 0. Your code can check waiting.length instead of guessing that a second item exists.
Step through rest in an object pattern and in an array pattern below.
Find the dots that gather leftovers. Then step through the object pattern and the array pattern.
script
const { password, ...safeUser } = user;const steps = ["draft", "review", "publish"];const [first, ...others] = steps;console.log(JSON.stringify(safeUser));console.log(first + " / " + others.join(", "));Both patterns are common in real code:
- The object pattern
{ password, ...safeUser }leaves one property out and keeps all the others insafeUser. - The array pattern
[first, ...others]keeps the first item apart and keeps the other items together inothers.
const [first, ...middle, last] = list; is a SyntaxError, an error JavaScript reports before the code runs. Rest means "gather everything that is left". After it, nothing is left, so JavaScript does not allow another variable after rest in the same pattern.
Merging & copying
Spread makes a new outer array or object. But the copy is shallow: it copies only the outer level. If a property holds another object, the copy points to that same inner object. You saw this idea in the Objects & references lesson: two variables can point to one object.
const profile = { name: "Asha", preferences: { theme: "dark" } };
const copy = { ...profile };
copy.preferences.theme = "light";
console.log(profile.preferences.theme);Line 2 makes a new outer object, copy. But copy.preferences still points to the same inner object as profile.preferences. Line 3 changes that shared inner object. So line 4 prints light, even though the code never touched profile by name.
{ ...profile } makes a new outer object, copy. But its preferences property points to the same inner object as before. Change the theme through copy, and profile sees the change too.To keep a change apart from the old object, copy the inner level too. An update that makes new objects instead of changing the old ones is called an immutable update.
const state = { user: { name: "Grace", role: "admin" }, tags: ["js", "data"]};const nextState = { ...state, user: { ...state.user, name: "Ada" }, tags: [...state.tags, "spread"]};console.log(state.user.name);console.log(nextState.user.name);console.log(state.tags === nextState.tags);The rule: copy every object or array on the way to the value you change. Lines 6 to 8 do that:
- Line 6 copies the outer
stateobject. - Line 7 copies the inner
userobject, then sets a newname. - Line 8 copies the
tagsarray and adds one more tag.
Line 10 still prints Grace, line 11 prints Ada, and line 12 prints false because the two tags arrays are different arrays. Tools for screen updates, such as React and Redux, use this style. A new object is easy to spot as a change, so the tool knows what to redraw.
The table compares four ways to copy. Read each row: the tool, how deep it copies, when to use it, and what to watch for.
| Tool | How deep does it copy? | When is it a good fit? | What should you watch for? |
|---|---|---|---|
[...items] | Only the outer level. | Copying or rebuilding an array. | Inner objects are still shared. |
{ ...object } | Only the outer level. | Merging settings and updating state. | A later property replaces an earlier one. |
Object.assign({}, a, b) | Only the outer level. | Older code that merges objects. | It also copies left to right, and it changes its first argument. |
structuredClone(value) | Every level, for the values it supports. | Making a fully separate copy of data. | It cannot copy functions, page elements, and a few other values. |
Where you’ll use this
You use spread and rest whenever you want new data without changing the old data. These four patterns come up again and again:
- Merge defaults with user settings:
{ ...defaults, ...userSettings }keeps every default the user did not set, and the user's choices win. - Leave out private fields:
const { password, ...safeUser } = user;gives you an object without the password, ready to show or log. - Add to the end without changing the old array:
[...items, newItem]returns a new array. - Insert or remove an item: spread the parts you want to keep, which you cut out with
slice.
Object rest leaves out only the top-level fields you name. It does not look inside inner objects. If a private value is inside user.profile, { password, ...safeUser } does not remove it. When you send data to other people, pick the fields you want to share, or clean each inner object too.
const todos = ["email", "shop"];
const nextTodos = [...todos, "walk"];
console.log(todos.join(" / ") + " -> " + nextTodos.join(" / "));Line 2 makes a new list with one more task. Line 3 prints email / shop -> email / shop / walk. The old list is still there, so you can use it for an undo button or to compare old and new.
The next block inserts and removes an item at position index. items.slice(start, end) returns a new array with the items from start up to, but not including, end.
const inserted = [ ...items.slice(0, index), newItem, ...items.slice(index)];const removed = [ ...items.slice(0, index), ...items.slice(index + 1)];- Lines 1 to 5: spread the items before
index, thennewItem, then the items fromindexon. - Lines 6 to 9: spread the items before
index, then the items after it. The item atindexis skipped.
Order rules & pitfalls
Most mistakes come from expecting the dots to do more than they do. Keep these rules in mind:
- Spread in an array or a call keeps the items in their order.
- Object spread copies from left to right, and a later property wins.
- Rest in destructuring must come last, because it gathers everything that is left.
- Object spread copies own enumerable properties only.
- Spread visits every item it copies, so a bigger list takes longer to copy.
Copying with spread takes longer the more items there are. A function call can also take only so many arguments. If you spread a very large array into a call, JavaScript can throw a RangeError. For very large data, use a loop, reduce, or a function that takes the whole array as one argument.
Common misconceptions
“Spread and rest are the same thing.”
They use the same three dots, but they work in opposite directions. Spread lays items out; rest gathers them. The code around the dots tells you which one you have.
“Object spread makes a deep copy.”
It makes a new outer object only. Inner objects and arrays are still shared, unless you copy those levels too or use structuredClone.
“Object spread copies every property.”
It copies own enumerable properties only. It skips borrowed and hidden properties, and it runs getters to copy the values they return.
“Spreading null always throws.”
Object spread skips null and undefined. Spread in an array or a call throws with them, because it needs an iterable.
“Spread is free because it is short to write.”
The code is short, but the work is still a full copy. For everyday data that is fine. For very large lists, measure how long it takes and pick the tool on purpose.
Practice: spread & rest
5 EXERCISESGuess the output first. Then run the code in your console or editor. Each answer is checked against what the code really prints.
What does the program print? Then run it to check.
const defaults = { theme: "light", density: "comfortable" };
const user = { density: "compact" };
const settings = { ...defaults, ...user };
console.log(settings.theme + " / " + settings.density);const defaults = { theme: "light", density: "comfortable" };
const user = { density: "compact" };
const settings = { ...defaults, ...user };
console.log(settings.theme + " / " + settings.density);settings.theme comes from defaults, because user has no theme. settings.density is compact, because user is copied later.
Object rest keeps the safe data and leaves the secret out. Which keys are left?
const user = { name: "Ada", password: "secret", role: "admin" };
const { password, ...publicUser } = user;
console.log(Object.keys(publicUser).join(","));const user = { name: "Ada", password: "secret", role: "admin" };
const { password, ...publicUser } = user;
console.log(Object.keys(publicUser).join(","));password gets the secret value. publicUser gathers the properties that are left: name and role.
Guess the output. Notice that the old name stays the same.
const state = { user: { name: "Grace", score: 10 }, theme: "dark" };
const next = { ...state, user: { ...state.user, name: "Ada" } };
console.log(state.user.name + " -> " + next.user.name);const state = { user: { name: "Grace", score: 10 }, theme: "dark" };
const next = { ...state, user: { ...state.user, name: "Ada" } };
console.log(state.user.name + " -> " + next.user.name);The old state still says Grace. The copied inner object says Ada. Both levels were copied, so the old inner object did not change.
Enter both printed lines in order.
const user = { name: "Ada" };
console.log(({ ...user, name: "Lin" }).name);
console.log(({ name: "Lin", ...user }).name);const user = { name: "Ada" };
console.log(({ ...user, name: "Lin" }).name);
console.log(({ name: "Lin", ...user }).name);The first object copies user, then writes name: "Lin", so it prints Lin. The second object writes Lin first, then copies user, so it prints Ada.
Check that the old array and the new array print differently.
const items = ["draft", "review"];
const nextItems = [...items, "publish"];
console.log(items.join("/") + " -> " + nextItems.join("/"));const items = ["draft", "review"];
const nextItems = [...items, "publish"];
console.log(items.join("/") + " -> " + nextItems.join("/"));[...items, "publish"] lays out the old items in a new array and adds one more. The old array does not change.
Quiz: check your understanding
7 QUESTIONSEvery choice explains why it is right or wrong. For code questions, first look at the code around the dots: do they lay items out or gather leftovers?
Question 1 of 7Which sentence is the best way to remember the two jobs of
...?Choose an answer to see the explanation.
Question 2 of 7What does this array spread code print?
Read the code, then predictconst a = ["x", "y"]; console.log([0, ...a, 3].join(""));Choose an answer to see the explanation.
Question 3 of 7What does this object merge code print?
Read the code, then predictconst defaults = { theme: "light", size: 20 }; const user = { theme: "dark" }; console.log(({ ...defaults, ...user }).theme);Choose an answer to see the explanation.
Question 4 of 7What does this object rest code print?
Read the code, then predictconst user = { name: "Ada", password: "secret", role: "admin" }; const { password, ...safeUser } = user; console.log(Object.keys(safeUser).join("/"));Choose an answer to see the explanation.
Question 5 of 7Which line is invalid JavaScript?
Read the code, then predictconst [first, ...middle, last] = [1, 2, 3];Choose an answer to see the explanation.
Question 6 of 7What does this copy code print?
Read the code, then predictconst state = { user: { name: "Grace" } }; const copy = { ...state }; copy.user.name = "Ada"; console.log(state.user.name);Choose an answer to see the explanation.
Question 7 of 7Which statement about object spread is true?
Choose an answer to see the explanation.
Key takeaways
- Spread lays items out; rest gathers leftovers.
- Spread in an array or a call lays out the items of an iterable, in order.
- Object spread copies own enumerable properties, and a later property wins.
- Rest in destructuring gathers what is left, and it must come last.
- Spread copies only the outer level, and bigger copies take longer. Copy inner levels on purpose.
Remember the one-liner.
Spread lays values out; rest gathers the leftovers back in.
Up next: Map & Set. You'll meet two collections: one that keeps each value only once, and one that can use any value as a key.