[Medium] Deep Clone
1. What is Deep Clone?
What is Deep Clone?
Deep Clone creates a new object and recursively copies all properties of the original object, including all nested objects and arrays. After a deep clone, the new object is completely independent from the original — modifying one does not affect the other.
Shallow Clone vs Deep Clone
Shallow Clone: Only copies the first level of an object's properties. Nested objects still share references.
// Shallow clone example
const original = {
name: 'John',
address: {
city: 'Taipei',
country: 'Taiwan',
},
};
const shallowCopy = { ...original };
shallowCopy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Kaohsiung' ❌ Original object was also modified
Deep Clone: Recursively copies all levels of properties, fully independent.
// Deep clone example
const original = {
name: 'John',
address: {
city: 'Taipei',
country: 'Taiwan',
},
};
const deepCopy = deepClone(original);
deepCopy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Taipei' ✅ Original object is unaffected
2. Implementation Methods
Implementation Methods
Method 1: Using JSON.parse and JSON.stringify
Pros: Simple and quick Cons: Cannot handle functions, undefined, Symbol, Date, RegExp, Map, Set, and other special types
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
// Test
const original = {
name: 'John',
age: 30,
address: {
city: 'Taipei',
country: 'Taiwan',
},
hobbies: ['reading', 'coding'],
};
const cloned = deepClone(original);
cloned.address.city = 'Kaohsiung';
cloned.hobbies.push('swimming');
console.log(original.address.city); // 'Taipei' ✅
console.log(original.hobbies); // ['reading', 'coding'] ✅
Limitations:
const obj = {
date: new Date(),
func: function () {},
undefined: undefined,
symbol: Symbol('test'),
regex: /test/g,
};
const cloned = deepClone(obj);
console.log(cloned.date); // {} ❌ Date becomes an empty object
console.log(cloned.func); // undefined ❌ Function is lost
console.log(cloned.undefined); // undefined ✅ But JSON.stringify removes it
console.log(cloned.symbol); // undefined ❌ Symbol is lost
console.log(cloned.regex); // {} ❌ RegExp becomes an empty object
Method 2: Recursive Implementation (handling basic types and objects)
function deepClone(obj) {
// Handle null and primitive types
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Handle RegExp
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
// Handle objects
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// Test
const original = {
name: 'John',
date: new Date(),
regex: /test/g,
hobbies: ['reading', 'coding'],
address: {
city: 'Taipei',
},
};
const cloned = deepClone(original);
cloned.date.setFullYear(2025);
cloned.hobbies.push('swimming');
console.log(original.date.getFullYear()); // 2024 ✅ Unaffected
console.log(original.hobbies); // ['reading', 'coding'] ✅
Method 3: Complete Implementation (handling Map, Set, Symbol, etc.)
function deepClone(obj, map = new WeakMap()) {
// Handle null and primitive types
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle circular references
if (map.has(obj)) {
return map.get(obj);
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Handle RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Handle Map
if (obj instanceof Map) {
const clonedMap = new Map();
map.set(obj, clonedMap);
obj.forEach((value, key) => {
clonedMap.set(deepClone(key, map), deepClone(value, map));
});
return clonedMap;
}
// Handle Set
if (obj instanceof Set) {
const clonedSet = new Set();
map.set(obj, clonedSet);
obj.forEach((value) => {
clonedSet.add(deepClone(value, map));
});
return clonedSet;
}
// Handle arrays
if (Array.isArray(obj)) {
const clonedArray = [];
map.set(obj, clonedArray);
obj.forEach((item) => {
clonedArray.push(deepClone(item, map));
});
return clonedArray;
}
// Handle objects
const cloned = {};
map.set(obj, cloned);
// Handle Symbol properties
const symbolKeys = Object.getOwnPropertySymbols(obj);
const stringKeys = Object.keys(obj);
// Copy regular properties
stringKeys.forEach((key) => {
cloned[key] = deepClone(obj[key], map);
});
// Copy Symbol properties
symbolKeys.forEach((symbolKey) => {
cloned[symbolKey] = deepClone(obj[symbolKey], map);
});
return cloned;
}
// Test
const symbolKey = Symbol('test');
const original = {
name: 'John',
[symbolKey]: 'symbol value',
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
hobbies: ['reading', 'coding'],
};
const cloned = deepClone(original);
console.log(cloned[symbolKey]); // 'symbol value' ✅
console.log(cloned.map.get('key')); // 'value' ✅
console.log(cloned.set.has(1)); // true ✅
Method 4: Handling Circular References
function deepClone(obj, map = new WeakMap()) {
// Handle null and primitive types
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle circular references
if (map.has(obj)) {
return map.get(obj);
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Handle RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Handle arrays
if (Array.isArray(obj)) {
const clonedArray = [];
map.set(obj, clonedArray);
obj.forEach((item) => {
clonedArray.push(deepClone(item, map));
});
return clonedArray;
}
// Handle objects
const cloned = {};
map.set(obj, cloned);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key], map);
}
}
return cloned;
}
// Test circular references
const original = {
name: 'John',
};
original.self = original; // Circular reference
const cloned = deepClone(original);
console.log(cloned.self === cloned); // true ✅ Circular reference handled correctly
console.log(cloned !== original); // true ✅ It's a different object
3. Common Interview Questions
Common Interview Questions
Question 1: Basic Deep Clone Implementation
Implement a deepClone function that can deep clone objects and arrays.
Click to reveal answer
function deepClone(obj) {
// Handle null and primitive types
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Handle RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
// Handle objects
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
// Test
const original = {
name: 'John',
age: 30,
address: {
city: 'Taipei',
country: 'Taiwan',
},
hobbies: ['reading', 'coding'],
};
const cloned = deepClone(original);
cloned.address.city = 'Kaohsiung';
cloned.hobbies.push('swimming');
console.log(original.address.city); // 'Taipei' ✅
console.log(original.hobbies); // ['reading', 'coding'] ✅
Question 2: Handling Circular References
Implement a deepClone function that can handle circular references.
Click to reveal answer
function deepClone(obj, map = new WeakMap()) {
// Handle null and primitive types
if (obj === null || typeof obj !== 'object') {
return obj;
}
// Handle circular references
if (map.has(obj)) {
return map.get(obj);
}
// Handle Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// Handle RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// Handle arrays
if (Array.isArray(obj)) {
const clonedArray = [];
map.set(obj, clonedArray);
obj.forEach((item) => {
clonedArray.push(deepClone(item, map));
});
return clonedArray;
}
// Handle objects
const cloned = {};
map.set(obj, cloned);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key], map);
}
}
return cloned;
}
// Test circular references
const original = {
name: 'John',
};
original.self = original; // Circular reference
const cloned = deepClone(original);
console.log(cloned.self === cloned); // true ✅
console.log(cloned !== original); // true ✅
Key points:
- Use
WeakMapto track already-processed objects - Check whether an object already exists in the map before creating a new one
- If it exists, return the reference from the map to avoid infinite recursion
Question 3: Limitations of JSON.parse and JSON.stringify
Explain the limitations of using JSON.parse(JSON.stringify()) for deep cloning, and provide solutions.
Click to reveal answer
Limitations:
-
Cannot handle functions
const obj = { func: function () {} };
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.func); // undefined ❌ -
Cannot handle undefined
const obj = { value: undefined };
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.value); // undefined (but the property is removed) ❌ -
Cannot handle Symbol
const obj = { [Symbol('key')]: 'value' };
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned); // {} ❌ Symbol property is lost -
Date becomes a string
const obj = { date: new Date() };
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.date); // "2024-01-01T00:00:00.000Z" ❌ Becomes a string -
RegExp becomes an empty object
const obj = { regex: /test/g };
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.regex); // {} ❌ Becomes an empty object -
Cannot handle Map, Set
const obj = { map: new Map([['key', 'value']]) };
const cloned = JSON.parse(JSON.stringify(obj));
console.log(cloned.map); // {} ❌ Becomes an empty object -
Cannot handle circular references
const obj = { name: 'John' };
obj.self = obj;
JSON.parse(JSON.stringify(obj)); // ❌ Error: Converting circular structure to JSON
Solution: Use a recursive implementation with special handling for different types.
4. Best Practices
Best Practices
Recommended Approaches
// 1. Choose the appropriate method based on requirements
// For basic objects and arrays only, use a simple recursive implementation
function simpleDeepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (Array.isArray(obj)) return obj.map((item) => simpleDeepClone(item));
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = simpleDeepClone(obj[key]);
}
}
return cloned;
}
// 2. For complex types, use the complete implementation
function completeDeepClone(obj, map = new WeakMap()) {
// ... complete implementation
}
// 3. Use WeakMap to handle circular references
// WeakMap doesn't prevent garbage collection, making it suitable for tracking object references
Approaches to Avoid
// 1. Don't overuse JSON.parse(JSON.stringify())
// ❌ Loses functions, Symbol, Date, and other special types
const cloned = JSON.parse(JSON.stringify(obj));
// 2. Don't forget to handle circular references
// ❌ Will cause stack overflow
function deepClone(obj) {
const cloned = {};
for (let key in obj) {
cloned[key] = deepClone(obj[key]); // Infinite recursion
}
return cloned;
}
// 3. Don't forget to handle Date, RegExp, and other special types
// ❌ These types require special handling
5. Interview Summary
Interview Summary
Quick Reference
Deep Clone:
- Definition: Recursively copies an object and all its nested properties, creating a fully independent copy
- Methods: Recursive implementation, JSON.parse(JSON.stringify()), structuredClone()
- Key points: Handle special types, circular references, Symbol properties
Implementation Steps:
- Handle primitive types and null
- Handle Date, RegExp, and other special objects
- Handle arrays and objects
- Handle circular references (using WeakMap)
- Handle Symbol properties
Interview Answer Example
Q: Please implement a Deep Clone function.
"Deep clone creates a completely independent new object by recursively copying all nested properties. My implementation first handles primitive types and null, then applies special handling for different types like Date, RegExp, arrays, and objects. To handle circular references, I use a WeakMap to track already-processed objects. For Symbol properties, I use Object.getOwnPropertySymbols to retrieve and copy them. This ensures the deep-cloned object is fully independent from the original — modifying one won't affect the other."
Q: What are the limitations of JSON.parse(JSON.stringify())?
"The main limitations include: 1) Cannot handle functions — they get removed; 2) Cannot handle undefined and Symbol — these properties are ignored; 3) Date objects become strings; 4) RegExp becomes an empty object; 5) Cannot handle Map, Set, and other special data structures; 6) Cannot handle circular references — it throws an error. For these special cases, a recursive implementation should be used instead."