Spreading and Merging Objects

JavaScript ECMAScript 2024 · ✓ verified by execution on 2026-08-14

When building applications, you’ll constantly need to copy data, update state immutably, or combine multiple objects together. JavaScript makes this easy using the object spread syntax (...).

The spread syntax takes an object and “spreads” its individual properties into a brand new object.

javascript
const original = { x: 1, y: 2 };
const copy = { ...original };
console.log(copy.x, copy.y);
console.log(original === copy);
Output
1 2
false

Because copy is a brand new object in memory, comparing it to original with === evaluates to false.

Merging Objects

You can spread multiple objects into a single new object to merge them. But what happens if both objects share the exact same property key?

In an object spread, later properties overwrite earlier ones.

javascript
const defaultSettings = { theme: 'light', fast: true };
const userSettings = { theme: 'dark' };
const finalSettings = { ...defaultSettings, ...userSettings };
console.log(finalSettings.theme, finalSettings.fast);
Output
dark true

Because ...userSettings was placed after ...defaultSettings, the theme property was overwritten with 'dark', but the fast property was preserved.

The Shallow Copy Trap

A very dangerous misconception is that object spread creates a completely independent clone. Object spread only creates a shallow copy.

It effectively only goes one level deep. Any nested objects or arrays inside the parent object are copied by reference, not deeply cloned.

javascript
const original = { a: 1, nested: { b: 2 } };
const copy = { ...original };
copy.nested.b = 99;
console.log(original.nested.b);
Output
99

Wait, we modified copy.nested.b, so why did original.nested.b change to 99?

Let’s visualize the memory heap to see why this happens.

javascript · visualize
const state = { top: 1, sub: { nested: 2 } };
const copy = { ...state };
Original top: 1 sub: ref A Sub Object (Ref A) nested: 2 Copy (...spread) top: 1 sub: ref A
Spread is a shallow copy: the top-level properties are duplicated, but a nested object is shared — both the original and the copy still point at the same inner object.

As the diagram shows, copy.sub and original.sub both point to the exact same nested object in memory. Modifying one modifies the other!

Immutable Updates

Spread syntax shines when you need to add or update a property without mutating the original object—a common pattern in modern JavaScript frameworks.

javascript
const state = { score: 10, level: 1 };
const nextState = { ...state, score: 20 };
console.log(state.score);
console.log(nextState.score);
Output
10
20

By placing the new property score: 20 after ...state, you immutably generate a fresh state object with an updated score, while leaving the previous state completely intact.

Rest Properties

When combined with destructuring, the ... syntax is called the rest operator. While destructuring unpacks properties you ask for, the rest operator neatly packs everything else left over into a new object.

javascript
const user = { id: 1, name: 'Alice', age: 30 };
const { id, ...details } = user;
console.log(details.name, details.age);
console.log(details.id);
Output
Alice 30
undefined

By extracting id, the remaining properties (name and age) are bundled into the new details object.

Safe Spreading

If you try to destructure null or undefined, JavaScript throws a TypeError. However, spreading null or undefined into an object literal is completely safe!

Predict the output javascript

Read the code. What exactly will it print? Commit to an answer before you look.

const data = null;
const safeCopy = { ...data };
console.log(Object.keys(safeCopy).length);
Output
0

It silently evaluates to an empty object without crashing your application.

Check yourself

When using the object spread syntax `{ ...objA, ...objB }` to merge two objects, what happens if both objects share the exact same property key?

Reveal answer

The property from the object that comes later overwrites the earlier one. — Object properties that appear later in the literal overwrite any earlier properties with the exact same key.

Is the object created by spread syntax a deep or shallow copy?

Reveal answer

It is a shallow copy; top-level properties are copied but nested objects are shared by reference. — Spread syntax only copies one level deep. Any objects or arrays nested inside are shared by reference between the original and the new copy.

If you try to spread `null` into an object using `{ ...null }`, what is the result?

Reveal answer

It silently evaluates to an empty object `{}` without crashing. — Unlike destructuring, spreading `null` or `undefined` into an object literal is perfectly safe and just produces an empty object.

Challenges

Challenge 1 +25 XP

A developer is trying to load a system configuration by combining the `baseConfig` with the `envConfig`. However, they want the `envConfig` to override the defaults. Merge them correctly into `finalConfig`.

javascript
  • Test 1 — expects "localhost 3000\n"
Need a hint? (−25% XP)

Use `{ ...baseConfig, ...envConfig }` to merge the configurations, ensuring envConfig comes last.

Show solution (0 XP)
const baseConfig = { host: 'localhost', port: 8080 };
const envConfig = { port: 3000 };
const finalConfig = { ...baseConfig, ...envConfig };
console.log(finalConfig.host, finalConfig.port);

🐞 Bug Hunt +25 XP

This developer is trying to pull the `password` out of the user object so they can send the remaining safe properties to the client. But they put the rest operator in the wrong place! Fix the syntax error so `password` is extracted and the rest go into `safeUser`.

This code runs. It just does the wrong thing. Read it, find the defect, fix it — the tests below decide when you are right.

javascript
  • Test 1 — expects "admin true undefined\n"
Need a hint? (−25% XP)

The rest operator `...` must always be the very last element in the destructuring assignment.

Show solution (0 XP)
const user = { username: 'admin', password: '123', active: true };
const { password, ...safeUser } = user;
console.log(safeUser.username, safeUser.active, safeUser.password);