Suggest an editImprove this articleRefine the answer for “How to copy an Object in JavaScript?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Copying an object in JavaScript** produces a new object from existing data. Shallow copy (`{...obj}`) only duplicates the top level - nested objects remain shared references. Deep copy (`structuredClone`) clones every level independently. ```javascript const shallow = { ...original }; // nested refs are shared const deep = structuredClone(original); // fully independent copy const json = JSON.parse(JSON.stringify(original)); // deep, but drops functions and Date ``` **Key point:** mutating a nested property through a shallow copy also changes the original.Shown above the full answer for quick recall.Answer (EN)Image**Copying an object in JavaScript** creates a new object with the same data, but the behavior depends on whether you do a shallow or deep copy. ## Theory ### TL;DR - Shallow copy is like photocopying a letter with a sealed envelope: you get the letter, but the envelope still points to the original contents - Deep copy photocopies everything, envelope included, with new contents inside - Shallow only duplicates the top level; nested objects stay as shared references - Flat object with no nesting: use `{...obj}`. Nested objects that change independently: use `structuredClone` - `JSON.parse(JSON.stringify(obj))` is deep but drops functions, `Date`, `Map`, and `undefined` ### Quick example ```javascript const original = { user: { name: 'Alice' }, id: 1 }; // Shallow - nested object is a shared reference const shallow = { ...original }; shallow.user.name = 'Bob'; console.log(original.user.name); // "Bob" - original was mutated! // Deep - nested object is fully independent const deep = structuredClone(original); deep.user.name = 'Charlie'; console.log(original.user.name); // "Alice" - original is safe ``` Changing `shallow.user.name` also changes `original.user.name` because both point to the same object in memory. `structuredClone` breaks that link entirely. ### The core difference Shallow copy duplicates only the top-level property values. Primitives (strings, numbers, booleans) get copied by value, so they are independent. But nested objects and arrays are copied by reference: both the original and the copy point to the same memory address. Change the nested data through the copy, and the original changes too. Deep copy walks the entire structure recursively and creates new objects at every level. No shared references. Mutations stay isolated. ### When to use - Flat config object with no nesting: `{...obj}` or `Object.assign({}, obj)` - React state with nested objects: `structuredClone(state)` before updating - JSON-only data (no Dates, functions, Maps): `JSON.parse(JSON.stringify(obj))` - Object contains `Date`, `Map`, or `Set`: `structuredClone` (Chrome 98+, Node 17.5+) - Legacy environment or complex custom classes: `lodash.cloneDeep` - Shared read-only data that never mutates: shallow is fine and faster ### Comparison table | Method | Type | Handles Functions? | Handles Date/Map/Set? | Performance | |---|---|---|---|---| | `{...obj}` | Shallow | No | No (copies reference) | Fastest | | `Object.assign({}, obj)` | Shallow | No | No (copies reference) | Fast | | `JSON.parse(JSON.stringify(obj))` | Deep | No (drops them) | No (Date becomes string) | Medium | | `structuredClone(obj)` | Deep | No (throws error) | Yes | Fast | | `lodash.cloneDeep(obj)` | Deep | Yes | Yes | Slowest | ### How V8 handles copying Spread and `Object.assign` iterate own enumerable keys using the `[[GetOwnPropertyKeys]]` internal method and copy values by reference without recursion. That is why they are fast, and that is why they stop at the first level. `JSON.stringify` serializes the object to a string, traversing the structure and skipping non-enumerable properties, functions, and `undefined`. Then `JSON.parse` rebuilds a new object tree from scratch. `structuredClone` uses the HTML Structured Clone algorithm, the same mechanism behind `postMessage`, which handles `Date`, `Map`, `Set`, and `Blob` but explicitly rejects functions and `WeakMap`. ### Common mistakes **1. Assuming spread copies nested objects** ```javascript // Buggy const state = { user: { prefs: {} } }; const copy = { ...state }; copy.user.prefs.dark = true; console.log(state.user.prefs.dark); // true - state was mutated // Fix: structuredClone(state) ``` **2. Using JSON on functions or undefined** ```javascript JSON.stringify({ fn: () => {}, undef: undefined }); // Result: "{}" - both properties disappear without any error // Fix: structuredClone or lodash.cloneDeep ``` **3. Date becomes a string after JSON roundtrip** ```javascript const obj = { created: new Date() }; const copy = JSON.parse(JSON.stringify(obj)); console.log(copy.created instanceof Date); // false - it is a string now // Fix: structuredClone keeps the Date instance intact ``` **4. Passing the target object directly to Object.assign** ```javascript // Buggy - modifies myObject directly Object.assign(myObject, source); // Fix Object.assign({}, myObject, source); ``` **5. Circular references** ```javascript const obj = {}; obj.self = obj; JSON.stringify(obj); // RangeError: circular structure structuredClone(obj); // DataCloneError // Fix: lodash.cloneDeep handles circular refs correctly ``` ### Real-world usage - React: `structuredClone(state)` for immutable nested state updates without mutation - Redux Toolkit: uses Immer internally, but `structuredClone` works for serializable state migration - Express middleware: `const data = structuredClone(req.body)` to avoid mutating the original request - Node.js: spread for simple config merging, `structuredClone` for worker thread data transfer - Lodash: `cloneDeep` used in over 1000 npm packages as the safe default for legacy projects In my experience, the most common production bug from this topic is mutating React state through a shallow copy of a nested object. The component re-renders but shows stale data because the reference to the nested object never actually changed. ### Follow-up questions **Q:** What does `original.b.c` print if you do `const copy = { ...{ a: 1, b: { c: 2 } } }` and then `copy.b.c = 3`? **A:** `original.b.c` becomes 3. Spread copies only the top level, so `b` is still a shared reference. **Q:** Why does `JSON.stringify` drop functions? **A:** JSON is a text format defined by RFC 8259. Functions are not part of that spec, so `JSON.stringify` skips them entirely without throwing. **Q:** Implement a shallow copy without any built-ins. **A:** `Object.keys(obj).reduce((acc, k) => { acc[k] = obj[k]; return acc; }, {})` iterates own keys and copies values one by one. **Q:** When does `structuredClone` throw? **A:** It throws `DataCloneError` for non-clonable types: `WeakMap`, `WeakSet`, functions, and DOM nodes. It also throws on circular references, unlike `lodash.cloneDeep` which handles them. **Q:** What happens to prototype properties and non-enumerable props with spread? **A:** Both are lost. Spread copies only own enumerable keys. If the original was created with `Object.create(proto)`, the copy has no prototype link. Properties defined with `Object.defineProperty(..., { enumerable: false })` are skipped too. ## Examples ### Shallow copy trap in a config object ```javascript const config = { server: { host: 'localhost', port: 3000 }, timeout: 5000 }; const devConfig = { ...config }; devConfig.timeout = 8000; // OK - primitive, independent copy devConfig.server.port = 4000; // Bad - mutates the original! console.log(config.timeout); // 5000 - safe console.log(config.server.port); // 4000 - mutated ``` Changing `timeout` is safe because it is a primitive and primitives are copied by value. Changing `server.port` mutates the original because `config.server` and `devConfig.server` point to the same object. To protect nested data, replace spread with `structuredClone(config)`. ### React state update with structuredClone ```javascript const [userData, setUserData] = useState({ profile: { name: 'Alice', settings: { theme: 'dark' } } }); // Wrong: spread does not protect nested settings const badUpdate = { ...userData }; badUpdate.profile.settings.theme = 'light'; setUserData(badUpdate); // original profile.settings was already mutated here // Correct: structuredClone creates a fully independent copy const goodUpdate = structuredClone(userData); goodUpdate.profile.settings.theme = 'light'; setUserData(goodUpdate); // userData.profile.settings.theme stays 'dark' until React processes the update ``` The wrong version mutates state before `setUserData` is even called. React may not detect the change because the reference to `profile` did not change, so the old and new state objects look identical to the reconciler. ### Edge case: prototypes, non-enumerable properties, and circular references ```javascript // Prototype and non-enumerable const original = Object.create({ proto: 'shared' }); original.own = { nest: 'value' }; Object.defineProperty(original, 'hidden', { value: 'secret', enumerable: false }); const spread = { ...original }; console.log(spread.proto); // undefined - prototype link lost console.log(spread.hidden); // undefined - non-enumerable skipped const json = JSON.parse(JSON.stringify(original)); console.log(json.proto); // undefined const cloned = structuredClone(original); console.log(cloned.proto); // 'shared' - structuredClone preserves the prototype chain // Circular reference const circular = { name: 'test' }; circular.self = circular; // JSON.stringify(circular) - RangeError // structuredClone(circular) - DataCloneError import _ from 'lodash'; const safe = _.cloneDeep(circular); // handles it correctly ``` Only `structuredClone` preserves the prototype chain from `Object.create`. Only `lodash.cloneDeep` survives circular references without throwing. These two edge cases are where spread and JSON both fail silently or with errors.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.