Deep copy vs shallow copy — behavior and how to achieve each
Shallow copy duplicates the top level; nested objects are still shared references. Deep copy recursively duplicates every level. Use `structuredClone` for a correct, fast deep copy.
A shallow copy creates a new object whose top-level properties are copied, but any property that is itself an object/array still points to the same memory as the original. Mutating a nested value visibly affects both.
A deep copy walks the entire tree and copies every nested object, so the result is fully independent.
Common ways to make each:
- Shallow:
{...obj},Object.assign({}, obj),arr.slice(),Array.from(arr). - Deep:
structuredClone(obj)(built-in, handles cycles, Maps, Sets, typed arrays, Dates), or for plain JSON:JSON.parse(JSON.stringify(obj))(loses functions, undefined, Dates become strings, throws on cycles).
structuredClone is the right answer in any modern environment. Mention it before JSON.parse(JSON.stringify()).
Where this matters in React: setting state with setState({ ...prev, nested: prev.nested }) is shallow — mutating prev.nested inside still mutates state and breaks reconciliation. Use the immutable update pattern or a library like Immer.
Code
Follow-up questions
- •Why does JSON.parse(JSON.stringify()) lose functions and undefined?
- •How do you deep-copy an object with cycles?
- •How does structural sharing (Immer / Immutable.js) avoid full deep copies?
Common mistakes
- •Believing `{ ...obj }` is a deep copy.
- •Using JSON round-trip on data with Dates / Maps / undefined / functions and being surprised.
- •Mutating state that came from a shallow spread.
Performance considerations
- •structuredClone is O(n) but ships in C++ — usually faster than the JSON trick and safer.
- •Deep cloning huge state trees per change is wasteful; prefer structural sharing (Immer) or normalized state.
Edge cases
- •structuredClone throws on functions, DOM nodes, class instances with private fields, and Symbol-keyed properties.
- •Cyclic references break JSON.stringify but not structuredClone.
Real-world examples
- •Redux/Zustand state updates rely on shallow-copy-with-replacement — that's why mutation breaks ===-based selectors.
- •Immer uses copy-on-write proxies to give you ergonomic mutation with a structurally-shared deep copy result.