Suggest an editImprove this articleRefine the answer for “Shallow copy vs deep copy in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Shallow copy** copies top-level properties but shares references to nested objects. **Deep copy** clones the full structure independently at every level. ```javascript const obj = { a: 1, nested: { b: 2 } }; const shallow = { ...obj }; // nested.b is shared const deep = structuredClone(obj); // fully independent shallow.nested.b = 99; console.log(obj.nested.b); // 99 - original affected ``` **Key point:** use spread (`{ ...obj }`) for flat data, `structuredClone()` for nested objects.Shown above the full answer for quick recall.Answer (EN)Image**Shallow copy** creates a new object with copies of top-level properties, but nested objects stay shared. **Deep copy** clones the full structure at every level so nothing is shared between the original and the clone. ## Theory ### TL;DR - Shallow copy is like photocopying a letter with attached photos: the letter is new, but both copies point to the same photos - Deep copy photocopies the photos too - nothing shared - `{ ...obj }` and `Object.assign()` are shallow; `structuredClone()` and `lodash.cloneDeep()` are deep - Flat object with no nesting? Shallow is enough. Nested state or API payloads? Use deep. - `structuredClone()` is the modern default for deep copying, available in all browsers since 2022 and Node.js 17+ ### Quick example ```javascript const original = { name: 'Alice', scores: [90, 85] }; const shallow = { ...original }; // shallow copy const deep = structuredClone(original); // deep copy shallow.name = 'Bob'; shallow.scores[0] = 100; console.log(original.name); // 'Alice' - string copied by value console.log(original.scores[0]); // 100 - array is shared! console.log(deep.scores[0]); // 90 - fully independent ``` `shallow.name` and `original.name` are independent because strings are primitives copied by value. But `shallow.scores` and `original.scores` point to the same array in memory, so any mutation on one affects the other. ### Key difference Shallow copy allocates a new object and iterates enumerable own properties. Primitives (strings, numbers, booleans) are copied by value. Objects and arrays at any deeper level are assigned by reference - both the original and the copy point to the same memory location. Deep copy traverses the full structure recursively, creating new objects and arrays at every level so the two structures share nothing. ### When to use - Config object with no nested data - shallow with `{ ...obj }` or `Object.assign` - React component state with nested fields like `address.city` or `coords[]` - deep copy to isolate updates and avoid stale references - API response you cache and then modify - `structuredClone` keeps the cache intact - Performance-critical loops where nested data is read-only - shallow wins here (top-level iteration vs full recursive traversal) ### Comparison table | Method | Type | Functions | Date | Circular refs | Best for | |---|---|---|---|---|---| | `{ ...obj }` | Shallow | kept | kept | N/A | Flat objects, React state spreads | | `Object.assign()` | Shallow | kept | kept | N/A | Merging flat configs | | `structuredClone()` | Deep | throws | preserved | handled | Modern apps, Node 17+, Worker messages | | `JSON.parse/stringify` | Deep | stripped | becomes string | throws | Simple objects with no special types | | `lodash.cloneDeep()` | Deep | cloned | preserved | handled | Legacy codebases, complex nested data | ### How V8 handles this V8 represents objects as hash maps with property descriptors and pointers. The spread operator iterates enumerable own properties via the `[[GetOwnProperty]]` internal method, copies primitive values directly, and reassigns pointers for nested objects without touching them. `structuredClone()` uses the HTML Structured Clone Algorithm: it traverses the full tree, creates new objects at every level, and tracks circular references with an internal map. That same tracking is why it throws on functions - they are not serializable by the algorithm. `lodash.cloneDeep` tracks visited nodes using a stack and creates new objects via `Object.create(null)`, making it the most permissive option for edge cases. ### Common mistakes **1. Assuming spread copies nested objects** ```javascript const state = { user: { prefs: [1, 2] } }; const copy = { ...state }; copy.user.prefs.push(3); console.log(state.user.prefs); // [1, 2, 3] - original mutated ``` Spread only goes one level deep. `copy.user` and `state.user` are the same reference. Fix: `const copy = structuredClone(state)`. **2. Using JSON.parse/stringify with Dates or functions** ```javascript const obj = { name: 'Alice', created: new Date(), greet: () => 'hi' }; const copy = JSON.parse(JSON.stringify(obj)); console.log(typeof copy.created); // 'string' - Date became a string console.log(copy.greet); // undefined - function stripped ``` `structuredClone` preserves `Date` correctly. For functions, there is no built-in solution - use `lodash.cloneDeep` or handle them manually. **3. Using .slice() and thinking nesting is handled** ```javascript const arr = [1, [2, 3]]; const copy = arr.slice(); // shallow - nested arrays still shared copy[1][0] = 99; console.log(arr[1][0]); // 99 - original mutated ``` `.slice()`, spread, and `Array.from` all create shallow copies of arrays. None of them touch nested arrays. **4. Circular references blow up JSON** ```javascript const obj = { name: 'test' }; obj.self = obj; JSON.parse(JSON.stringify(obj)); // Throws: Converting circular structure to JSON structuredClone(obj); // Works fine ``` If your data might be self-referential, `structuredClone` or `lodash.cloneDeep` are the only safe options. **5. Non-enumerable properties disappear on spread** ```javascript const obj = { a: 1 }; Object.defineProperty(obj, 'b', { value: 2, enumerable: false }); const copy = { ...obj }; console.log(copy.b); // undefined ``` Spread and `Object.assign` skip non-enumerable properties. Easy to miss when copying objects that have hidden metadata attached via `defineProperty`. ### Real-world usage - **React**: spread for flat state updates (`{ ...user, name: 'Bob' }`); `structuredClone` for nested form data before submission - **Redux Toolkit**: `createSlice` deep clones initial state internally to protect the store from accidental mutations - **next-auth.js**: uses `lodash.cloneDeep` for session objects passed through middleware layers - **Express**: `express-validator` clones `req.body` before validation - shallow for flat bodies, deep for nested schemas - **Node.js workers**: `structuredClone` is the standard way to pass data to `Worker` threads without shared memory ### Follow-up questions **Q:** What prints here? `const a = [1]; const b = [a]; const c = [...b]; c[0][0] = 99; console.log(a[0]);` **A:** 99. Spreading `b` is shallow, so `c[0]` and `a` are the same array. The mutation goes all the way back to `a`. **Q:** What does `structuredClone` do with functions? **A:** It throws a `DataCloneError`. Functions are not serializable by the Structured Clone Algorithm. Use `lodash.cloneDeep` or a custom recursive clone if the object contains functions. **Q:** How does `lodash.cloneDeep` handle circular references? **A:** It tracks visited objects using a stack. When it encounters an already-visited reference, it reuses the already-cloned version instead of recursing forever. JSON fails completely here; `structuredClone` handles it with an internal reference map. **Q:** Is `Object.assign({}, obj)` different from `{ ...obj }`? **A:** For plain flat objects the result is the same. The difference: `Object.assign` invokes setters on the target if they exist; spread does not. Both call getters on the source. **Q:** You need to pass a deeply nested 10,000-node object tree to a Web Worker. What do you use and why? **A:** Pass it directly via `postMessage` - workers use the Structured Clone Algorithm automatically, the same one `structuredClone` uses. Avoid JSON round-trips because they lose type info for Dates, Maps, Sets, and ArrayBuffers. For truly large payloads, look at `Transferable` objects for zero-copy transfers. ## Examples ### Shallow copy mutation in React state ```javascript const [user, setUser] = useState({ name: 'Alice', address: { city: 'NY', coords: [40.7, -74.0] } }); // Spread creates a new top-level object, but address is still shared const updated = { ...user, name: 'Bob' }; setUser(updated); // Some other code mutates updated.address later updated.address.city = 'LA'; console.log(user.address.city); // 'LA' - the original state object was affected ``` This is one of the most common bugs in React apps. The spread `{ ...user }` creates a new top-level object, but `updated.address` and `user.address` are still the same reference in memory. Any mutation to the nested object affects both. ### Deep copy with structuredClone ```javascript const [user, setUser] = useState({ name: 'Alice', address: { city: 'NY', coords: [40.7, -74.0] } }); // Fully isolated copy - nothing shared const updated = structuredClone(user); updated.address.city = 'LA'; updated.address.coords[0] = 34.0; setUser(updated); console.log(user.address.city); // 'NY' - original untouched console.log(user.address.coords[0]); // 40.7 - original untouched ``` `structuredClone` traverses the full tree. Dates, Maps, Sets, and ArrayBuffers are all preserved with their correct types. The one thing to watch: if the object contains functions, it throws a `DataCloneError`. ### Circular references and prototype chain (senior level) ```javascript const original = { name: 'test' }; original.self = original; // circular reference // JSON fails immediately try { JSON.parse(JSON.stringify(original)); } catch (e) { console.log(e.message); // Converting circular structure to JSON } // structuredClone handles it correctly const cloned = structuredClone(original); console.log(cloned !== original); // true - different object console.log(cloned.self === cloned); // true - self-reference preserved inside clone console.log(cloned.self !== original); // true - no link back to original ``` The circular reference in `cloned` points back to `cloned` itself, not to the original. That is correct behavior for a true deep clone. I've seen this trip up senior candidates who assumed `structuredClone` would fail on cycles the same way JSON does.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.