Skip to main content

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()) or cloneDeep() from Lodash.

Quick example

javascript
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 address property 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

javascript
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

javascript
const user = { name: 'Alice', age: 25 }; const clone = Object.assign({}, user); clone.name = 'Oleh'; console.log(user.name); // 'Alice', independent

A 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:

javascript
const clone = structuredClone(user); clone.address.city = 'Lviv'; console.log(user.address.city); // 'Kyiv', the original is untouched

JSON.parse(JSON.stringify()), universal but limited (it loses undefined and functions, turns Date into a string, and throws on circular references):

javascript
const clone = JSON.parse(JSON.stringify(user));

_.cloneDeep() from Lodash, reliable and compatible with every type:

javascript
import _ from 'lodash'; const clone = _.cloneDeep(user);

Summary: what Object.assign actually copies

What Object.assign() copiesBehaviour
Primitive values (string, number, boolean)copied by value
Nested objects and arrayscopied by reference
Functionscopied by reference
Deep structuresnot 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 as Object.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 drops undefined and functions, turns Date into a string, and throws on circular references.
  • Forgetting that Object.assign() mutates the target. The first argument is changed in place, so Object.assign(user, patch) is not copying, it is updating user.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.