# Spread and Rest

Assigning an object to a new variable doesn't copy it, so this lesson shows how spread makes real copies and merges, and how rest gathers any number of values into one.

Apprentice · JavaScript · Updated November 1, 2026

## After this lesson you can

- Explain why assigning an object to a second variable doesn't copy it
- Copy and merge arrays and objects with spread, leaving the original unchanged
- Gather leftover values with rest, in a function's parameters or when destructuring

## Key takeaways

- `const copy = pet;` doesn't copy anything: both names point at the same object, so a change through one shows up through the other.
- Spread, `...`, unpacks an object's properties or an array's items into a new object or array. `{ ...pet }` is a real copy.
- When properties are merged with spread, the one written later wins, so `{ ...pet, adopted: true }` is a copy with one change.
- A spread copy is shallow. An array inside the object is still shared, so copy it too, or use `structuredClone`.
- Rest uses the same three dots to gather. `...gifts` in a function's parameters collects every extra argument into one array.

An edit form with a **Cancel** button. An **Undo** that brings back what was there a second ago. A "preview your changes" screen before you save. All three need the same thing: a changed version of some data while the original stays exactly as it was. In JavaScript that sounds like it should be `const copy = pet;`, and that line is one of the most common bugs in the language. This lesson shows why, and how to make real copies with **spread**. Its twin, **rest**, gathers any number of values into one, and you'll see both in almost every framework you might learn next, React most of all.

## Two names, one pet

A pet at Happy Tails has one form, kept in one folder. If two volunteers each have a sticky note that says "Biscuit's form is in drawer 3", there are two notes but still only one form. Whatever one volunteer writes on the form, the other sees too.

A variable that holds an object works like that sticky note. It doesn't hold the object itself; it holds directions to it. So `const copy = pet;` makes a second note, not a second form:

```javascript
const pet = { name: 'Biscuit', species: 'dog', adopted: false };

const copy = pet;
copy.adopted = true;

console.log(pet.adopted);
console.log(copy === pet);
```

```text
true
true
```

Changing `copy` changed `pet`, because they are the same object. `copy === pet` (read "copy triple equals pet", or "strictly equals") is `true`: for objects, `===` asks "are these the very same object?", not "do they look alike?".

In your textbook this is called a **reference**: a variable holds a reference to the object. Numbers, strings and booleans don't behave this way; `let b = a;` with a number gives `b` its own copy of the value. Objects are shared like this, and so are arrays, which JavaScript counts as a kind of object.

![Two sticky notes labeled pet and copy, both with an arrow pointing at the same single adoption form for Biscuit](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/10/3bf4ae0497834768b2adb61cabd36257.webp)

## A real copy with spread

To get a second form, you need to copy every box onto a new one. **Spread syntax** does that. Three dots `...` (read "dot dot dot", or "spread") in front of an object, inside new curly braces, unpack all its properties into the new object:

```javascript
const pet = { name: 'Biscuit', species: 'dog', adopted: false };

const copy = { ...pet };
copy.adopted = true;

console.log(pet.adopted);
console.log(copy.adopted);
console.log(copy === pet);
```

```text
false
true
false
```

Now there are two objects. Read `{ ...pet }` as "a new object, with everything from `pet` spread into it". The curly braces are what make it new; the spread fills it.

## Copies with changes, and merges

The real power is copying and changing at the same time. Properties are written into the new object from left to right, and when a key appears twice, **the later one wins**. So a spread followed by a property is "a copy, with this one change", and two spreads in a row merge two objects:

```javascript
const pet = { name: 'Biscuit', species: 'dog', ageYears: 3, adopted: false };

const adoptedPet = { ...pet, adopted: true };
const updates = { ageYears: 4, nickname: 'Bis' };
const updatedPet = { ...pet, ...updates };

console.log(pet);
console.log(adoptedPet);
console.log(updatedPet);
```

```text
{ name: 'Biscuit', species: 'dog', ageYears: 3, adopted: false }
{ name: 'Biscuit', species: 'dog', ageYears: 3, adopted: true }
{
  name: 'Biscuit',
  species: 'dog',
  ageYears: 4,
  adopted: false,
  nickname: 'Bis'
}
```

`pet` is untouched. Order matters: `{ adopted: true, ...pet }` would let `pet`'s `adopted: false` win, because it comes later.

Spread works on arrays too, inside square brackets. It copies an array, joins arrays, or adds an item without changing the original:

```javascript
const dogs = ['Biscuit', 'Juniper'];
const cats = ['Mochi'];

const everyone = [...dogs, ...cats];
const withNewcomer = [...everyone, 'Pepper'];

console.log(everyone);
console.log(withNewcomer);
console.log(dogs);
```

```text
[ 'Biscuit', 'Juniper', 'Mochi' ]
[ 'Biscuit', 'Juniper', 'Mochi', 'Pepper' ]
[ 'Biscuit', 'Juniper' ]
```

Making a new value instead of changing the old one is called **immutability** (from *immutable*, "can't be changed"); changing an object in place is called **mutating** it. You'll meet both words in React's documentation, which asks you to update data this way.

**Try it.** In your `objects-practice` folder, make `copies.js`. Make a pet object, then a copy with spread that changes its `ageYears`. Print both, and check that the original's age didn't change.

## A copy is only one level deep

Spread copies the boxes on the form. If a box holds an array, such as `traits`, the copy gets the same array, not a new one. That's called a **shallow copy**, and it can surprise you:

```javascript
const pet = { name: 'Biscuit', traits: ['loves fetch'] };

const shallow = { ...pet };
shallow.traits.push('snores');
console.log(pet.traits);

const deep = structuredClone(pet);
deep.traits.push('steals socks');
console.log(pet.traits);
console.log(deep.traits);
```

```text
[ 'loves fetch', 'snores' ]
[ 'loves fetch', 'snores' ]
[ 'loves fetch', 'snores', 'steals socks' ]
```

Pushing onto `shallow.traits` changed `pet.traits`, because they're one array. There are two fixes:

- **Copy the inner array too**, with its own spread: `{ ...pet, traits: [...pet.traits, 'snores'] }`. This is the usual way when you're changing one inner list.
- **Make a deep copy** with `structuredClone(pet)`, which copies the object and everything inside it, all the way down.

## Rest: the same dots, gathering

Spread **unpacks** one thing into many. Its twin, **rest**, does the opposite: it **gathers** many things into one. Same three dots; the place they're written decides which one you get.

In a function's parameters, a **rest parameter** gathers every extra argument into an array. It has to be the last parameter, because it takes everything that's left:

```javascript
function welcomePack(petName, ...gifts) {
  return `${petName} goes home with ${gifts.length} gifts: ${gifts.join(', ')}`;
}

console.log(welcomePack('Mochi', 'a collar', 'a feather wand'));
console.log(welcomePack('Biscuit', 'a leash', 'a squeaky toy', 'treats'));
```

```text
Mochi goes home with 2 gifts: a collar, a feather wand
Biscuit goes home with 3 gifts: a leash, a squeaky toy, treats
```

Read `...gifts` as "the rest of the arguments, as `gifts`". The first argument goes to `petName`; everything after it lands in the `gifts` array. (`gifts.join(', ')` is the same `join` you met in *Arrays of Objects*: it glues the items into one string with a comma and a space between them.)

Rest works in destructuring too, where it gathers whatever you didn't name. That's a tidy way to leave a private field out of what a page shows. And spread goes the other way in a function call, unpacking an array into separate arguments:

```javascript
const ages = [3, 2, 1, 7];
console.log(Math.max(...ages));

const pet = { name: 'Pepper', species: 'rabbit', microchipId: '985-112-004' };
const { microchipId, ...publicProfile } = pet;

console.log(publicProfile);
```

```text
7
{ name: 'Pepper', species: 'rabbit' }
```

`Math.max` wants separate numbers, not an array (`Math.max(ages)` gives `NaN`, "not a number"), so `...ages` spreads them out. The rule of thumb: dots where values are **being made** (inside new `[ ]` or `{ }`, or in a call) spread; dots where variables are **being named** (in parameters, or on the left of `=`) gather.

## Check yourself

2 questions to try on the lesson page.

## So what

Knowing that `=` shares an object and spread copies it saves you from one of the most confusing bugs in JavaScript. And copy-with-a-change, `{ ...pet, adopted: true }`, is how modern apps update their data safely, from an Undo button to a whole React app.

## Key terms

- [Spread Syntax](https://www.ask-codey.com/glossary/spread-syntax)
- [Rest Parameter](https://www.ask-codey.com/glossary/rest-parameter)
- [Shallow Copy](https://www.ask-codey.com/glossary/shallow-copy-javascript)
- [Immutability](https://www.ask-codey.com/glossary/immutability)
- [structuredClone()](https://www.ask-codey.com/glossary/structuredclone)

## Cheat sheets

- [Spread and Rest](https://www.ask-codey.com/cheat-sheets/javascript-unpacking-spread-and-rest-cheat-sheet)

## Related

- [Destructuring](https://www.ask-codey.com/lessons/javascript-unpacking-destructuring)
- [Arrays of Objects](https://www.ask-codey.com/lessons/javascript-objects-arrays-of-objects)

Source: https://www.ask-codey.com/lessons/javascript-unpacking-spread-and-rest
