Copying objects and arrays in JavaScript is not straightforward.
The way you copy data structures affects whether changes to nested elements propagate to the original or remain isolated.
Reference vs Copy
First, let's clarify what is not a copy at all:
const arr = [1, 2, 3];
const arr2 = arr; // This is NOT a copy
arr2.push(4);
console.log(arr); // [1, 2, 3, 4] - original modified!
console.log(arr === arr2); // true - same reference
Both variables point to the same array in memory. Modifying one affects the other.
Shallow Copy
A shallow copy creates a new array or object, but only copies the first level. Nested objects or arrays remain references to the original.
const arr = [1, 2, 3];
// Method 1: Spread operator
const copy1 = [...arr];
// Method 2: Array.from()
const copy2 = Array.from(arr);
// Method 3: slice()
const copy3 = arr.slice();
// For objects
const obj = { a: 1, b: 2 };
const objCopy = { ...obj };
The Problem with Nested Structures
const arr = [1, 2, 3, [4], [6, 7, 8]];
const copy = [...arr];
// Top-level elements are independent
copy[0] = 999;
console.log(arr[0]); // 1 - unchanged
// Nested arrays are still references
copy[3].push(5);
console.log(arr[3]); // [4, 5] - modified!
console.log(copy[3]); // [4, 5]
copy[4][0] = 'changed';
console.log(arr[4]); // ['changed', 7, 8] - modified!
The nested arrays `[4] and [6, 7, 8] are shared between the original and the copy. Changes to nested elements affect both.
Deep Copy
A deep copy creates a completely independent copy, including all nested structures. Changes to any level don't affect the original.
Method 1: structuredClone (Recommended)
Available in Node.js v17.0.0+ and modern browsers:
const arr = [1, 2, 3, [4], [6, 7, 8]];
const deepCopy = structuredClone(arr);
deepCopy[3].push(5);
console.log(arr[3]); // [4] - unchanged ✓
console.log(deepCopy[3]); // [4, 5]
structuredClone handles:
- Dates, RegExp, Map, Set, ArrayBuffer
- Typed arrays
- Circular references
- Deep nesting
But it has limitations with:
- Functions (throws DataCloneError)
- DOM nodes (throws DataCloneError)
- Symbols
- Prototype chains
Method 2: JSON.parse(JSON.stringify())
The classic approach:
const obj = { a: 1, nested: { b: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));
JSON Method Limitations
JSON.parse has some limitations too:
// Functions are lost
const obj = { fn: () => 'hello', num: 1 };
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { num: 1 } - function gone
// Undefined variables are lost
const obj = { a: undefined, b: 2 };
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { b: 2 } - undefined removed
const obj = {
date: new Date(),
regex: /test/,
inf: Infinity
};
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy);
// {
// date: "2025-10-23T...", // becomes string
// regex: {}, // becomes empty object
// inf: null // becomes null
// }
When to Use Each Method
Use shallow copy when:
- Working with simple, flat data structures
- You only need to protect the top level
- Performance matters and you're certain there's no nesting
Use structuredClone when:
- You need a true deep copy
- Working with complex nested structures
- You need to handle Dates, Maps, Sets, or circular references
- Your data doesn't include functions or DOM nodes
Use JSON method when:
- You need to support older environments (pre-Node v17)
- Your data is simple and JSON-serializable
- You're certain there are no functions, undefined values, or special types
Rethink your approach when:
- You need to clone functions
- Your data structure is mixing behavior (functions) with data
- Consider separating your data models from your business logic