Q24 of 40 · JavaScript

What is the difference between a shallow copy and a deep copy in JavaScript?

JavaScriptMidjavascriptdeep-copyshallow-copyimmutabilitytest-data

Short answer

Short answer: A shallow copy duplicates the top-level structure but nested objects are still shared references. A deep copy recursively clones every level so changes to nested properties do not affect the original. JSON round-trip, structuredClone(), and recursive functions are common deep-copy approaches.

Detail

Understanding copy semantics is essential for managing test data and avoiding flaky tests caused by shared state.

Shallow copy: The top-level properties are copied to a new object, but nested objects or arrays still point to the same memory. Methods that produce shallow copies: object spread ({ ...obj }), Object.assign({}, obj), Array.slice(), Array.from(), and array spread ([...arr]).

Deep copy: Every level is recursively cloned. Modifications to any nested property do not affect the original. Approaches:

  • structuredClone(value) (Node 17+, modern browsers) — the recommended native method
  • JSON.parse(JSON.stringify(value)) — works for plain data but loses functions, Dates become strings, undefined is dropped
  • Utility libraries like Lodash _.cloneDeep() — handles edge cases reliably

When shallow copies cause test bugs: Shared test fixtures that mutate nested data across tests silently corrupt state. Each test should receive a freshly deep-cloned copy of the fixture.

Circular references: JSON.parse/stringify throws on circular structures; structuredClone handles them correctly.

// EXAMPLE

const original = { name: "Alice", address: { city: "Dublin" } };

// Shallow copy — nested object is shared
const shallow = { ...original };
shallow.address.city = "London";
console.log(original.address.city); // "London" — mutated!

// Deep copy with structuredClone (recommended)
const deep = structuredClone(original);
deep.address.city = "Paris";
console.log(original.address.city); // "Dublin" — unaffected

// JSON round-trip (limited — loses functions, Dates, undefined)
const jsonClone = JSON.parse(JSON.stringify(original));

// Test fixture helper
function freshFixture() {
  return structuredClone(BASE_USER); // each test gets an isolated copy
}

// WHAT INTERVIEWERS LOOK FOR

Knowing shallow vs deep with concrete examples. Recommending structuredClone over JSON round-trip. Connecting to test data isolation — shared mutable fixtures are a common source of flaky tests.

// COMMON PITFALL

Using `const copy = { ...original }` and assuming it is a full clone. Nested mutations through the copied reference are a silent source of test interference.