Back to JavaScript
JavaScript
medium
very high
mid

What is the difference between a deep copy and a shallow copy, and how do you 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.

5 min read·~10 min to think through

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

ts
const a = { user: { name: "Ada" } };
const b = { ...a };
b.user.name = "Bea";
console.log(a.user.name); // "Bea" — same reference
Shallow surprise
ts
const a = { user: { name: "Ada" }, when: new Date(), tags: new Set(["x"]) };
const b = structuredClone(a);
b.user.name = "Bea";
console.log(a.user.name); // "Ada" — independent
console.log(b.tags instanceof Set); // true — Set survived
Deep copy with structuredClone

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.

Senior engineer discussion

Senior signal: explain structural sharing, the cost of deep cloning vs reference equality for memoization, and when to normalize state instead of cloning it.