Copying with Object.assign
Object.assign() creates a shallow copy: it transfers only the source's own enumerable first-level properties into the target. That means nested objects and arrays are copied by reference, so the original and the copy keep sharing the very same inner structures.
Theory
TL;DR
Object.assign()gives you a shallow copy, not a deep one.- Only the first level of properties is copied.
- Primitives (
string,number,boolean) are copied by value and become independent. - Nested objects, arrays and functions are copied by reference.
- Changing a field inside a nested object is visible in both the original and the copy.
- For an independent copy use
structuredClone(),JSON.parse(JSON.stringify())orcloneDeep()from Lodash.
Quick example
const user = {
name: 'Alice',
address: {
city: 'Kyiv',
zip: 12345
}
};
const clone = Object.assign({}, user);
clone.address.city = 'Lviv';
console.log(user.address.city); // 'Lviv', not 'Kyiv'Why this happens:
Object.assign()copies only the first level of properties;- the
addressproperty is an object, so the reference to it is copied; - a change inside the nested object (
city) shows up in the original.
What this looks like in memory
user.address ────┐
│ (one and the same object)
clone.address ────┘Both objects, user and clone, point to one and the same address. So a change inside address affects both. What user and clone hold is not the address structure itself but the memory address of that structure, and Object.assign() honestly copied exactly that.
But primitive properties are copied properly
const user = { name: 'Alice', age: 25 };
const clone = Object.assign({}, user);
clone.name = 'Oleh';
console.log(user.name); // 'Alice', independentA primitive has no inner structure that could be shared, so the value itself lands in the copy. Only nested objects or arrays stay shared (and functions too, since a function is an object as well).
How to avoid the problem
If you need a deep copy, use one of these.
structuredClone(), the built-in modern way:
const clone = structuredClone(user);
clone.address.city = 'Lviv';
console.log(user.address.city); // 'Kyiv', the original is untouchedJSON.parse(JSON.stringify()), universal but limited (it loses undefined and functions, turns Date into a string, and throws on circular references):
const clone = JSON.parse(JSON.stringify(user));_.cloneDeep() from Lodash, reliable and compatible with every type:
import _ from 'lodash';
const clone = _.cloneDeep(user);Summary: what Object.assign actually copies
What Object.assign() copies | Behaviour |
|---|---|
Primitive values (string, number, boolean) | copied by value |
| Nested objects and arrays | copied by reference |
| Functions | copied by reference |
| Deep structures | not copied independently |
Common mistakes
- Treating
Object.assign({}, obj)as a full clone. It is only the top level; anything nested stays shared. - Assuming spread does something different.
{ ...obj }has exactly the same shallow semantics asObject.assign({}, obj). - Mutating a nested object in application state after "copying" it. In React or Redux this is a classic source of bugs: the reference did not change, so the component does not re-render, yet the original is already corrupted.
- Applying
JSON.parse(JSON.stringify())blindly. The trick silently dropsundefinedand functions, turnsDateinto a string, and throws on circular references. - Forgetting that
Object.assign()mutates the target. The first argument is changed in place, soObject.assign(user, patch)is not copying, it is updatinguser.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.