Lesson 71 of 80
{...obj} and Object.assign() both merge objects — but one can target an existing object, the other always creates a new one, and getters behave differently under each.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Object spread and Object.assign() both merge objects together, and for the common case — combining a few plain objects into a new one — they produce the same result. That's why people treat them as interchangeable. They aren't quite. Object.assign() mutates its first argument and returns it; spread always builds a brand-new object literal. That difference is invisible until you need one of them specifically, and then it matters a lot.
const defaults = { theme: "dark", retries: 3 };
const overrides = { retries: 5 };
// Spread: always a new object, defaults is untouched
const configA = { ...defaults, ...overrides };
console.log(defaults); // { theme: "dark", retries: 3 } — unchanged
// Object.assign: first argument gets mutated in place
const target = { theme: "dark", retries: 3 };
const configB = Object.assign(target, overrides);
console.log(target === configB); // true — same object
console.log(target); // { theme: "dark", retries: 5 } — mutated!
That mutation isn't a bug in Object.assign() — it's the point. It's how you fill in an object you already hold a reference to, without creating a new one:
function applyOverrides(target, patch) {
return Object.assign(target, patch); // patch existing object in place
}
You genuinely cannot do this with spread. { ...target, ...patch } always allocates a new object — there's no spread syntax that says "merge into this specific existing reference." If some other code is holding onto target and expects to see the update, spread won't give it to them; Object.assign() will.
The other real difference is getters. Spread reads a source getter's current value into a plain data property on the new object — the getter is gone afterward. Object.assign() does the same thing for assignment onto its target (it also just copies the value), but if the target's own property is a setter, Object.assign() will invoke that setter rather than overwrite it:
const source = {
get computed() { return Math.random(); }
};
const spread = { ...source }; // computed is a plain number now
const assigned = Object.assign({}, source); // same — value copied, getter gone
For plain data objects, spread reads better and is now the idiomatic default — it's an expression, so it fits inline in returns and arguments where Object.assign() needs a statement or an extra empty-object argument to avoid mutating something you didn't mean to.
Object.assign(a, b) mutates a and returns it — it is not "make a new merged object" unless you remember the leading {}. Forgetting this is the classic footgun: Object.assign(defaults, userOptions) silently corrupts your shared defaults object for every future caller.{ ...obj } and Object.assign({}, obj) copy top-level keys only — a nested object or array inside is still the same reference in the copy. Mutating copy.nested.value mutates the original too.Object.assign's first argument can. If you need to merge properties onto a specific object you already have a handle to — not a fresh literal — Object.assign() is the only one of the two that can do it.Both of these sit on top of the same idea as array destructuring and the spread/rest syntax used on function arguments — copying references, not values, for anything that isn't a primitive. Neither one is a substitute for a real deep clone; if you need that, you're reaching for structuredClone() or a recursive copy, not either of these.