frontend
Lodash DeepClone Implementation in JavaScript
January 24, 2026
Lodash DeepClone Implementation in JavaScript
Overview
Deep cloning creates a completely independent copy of an object, including all nested objects and arrays. Unlike shallow cloning, deep cloning ensures that changes to the cloned object don't affect the original object, even for deeply nested structures.
Basic Implementation
/**
* lodash deepclone()
* cloneDeep or deepclone polyfill
*/
function cloneDeep(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
const newArray = [];
for (let i = 0; i < obj.length; i++) {
newArray[i] = cloneDeep(obj[i]);
}
return newArray;
}
const newObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = cloneDeep(obj[key]);
}
}
return newObj;
}
// Usage
const obj = {
a: 55,
b: {
age: "value string",
c: {
random: "random string",
d: [1, 2, 6, 7],
},
},
};
const obj2 = cloneDeep(obj);
// Changing the nested values won't affect the original object
obj2.b.c.d[0] = "25";
console.log("old", obj); // Original unchanged
console.log("new", obj2); // New object with changes
Enhanced Implementation
Handling More Data Types
function cloneDeep(obj) {
// Handle null and undefined
if (obj === null || obj === undefined) {
return obj;
}
// Handle primitives
if (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 Array
if (Array.isArray(obj)) {
return obj.map(item => cloneDeep(item));
}
// Handle Object
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = cloneDeep(obj[key]);
}
}
return cloned;
}
With Circular Reference Handling
function cloneDeep(obj, visited = new WeakMap()) {
// Handle primitives
if (obj === null || typeof obj !== "object") {
return obj;
}
// Handle circular references
if (visited.has(obj)) {
return visited.get(obj);
}
// Handle Date
if (obj instanceof Date) {
const cloned = new Date(obj.getTime());
visited.set(obj, cloned);
return cloned;
}
// Handle Array
if (Array.isArray(obj)) {
const cloned = [];
visited.set(obj, cloned);
cloned.push(...obj.map(item => cloneDeep(item, visited)));
return cloned;
}
// Handle Object
const cloned = {};
visited.set(obj, cloned);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = cloneDeep(obj[key], visited);
}
}
return cloned;
}
Complete Implementation
function cloneDeep(obj, visited = new WeakMap()) {
// Handle null and undefined
if (obj === null || obj === undefined) {
return obj;
}
// Handle primitives (string, number, boolean, symbol, bigint)
if (typeof obj !== "object") {
return obj;
}
// Handle circular references
if (visited.has(obj)) {
return visited.get(obj);
}
// Handle Date
if (obj instanceof Date) {
const cloned = new Date(obj.getTime());
visited.set(obj, cloned);
return cloned;
}
// Handle RegExp
if (obj instanceof RegExp) {
const cloned = new RegExp(obj.source, obj.flags);
visited.set(obj, cloned);
return cloned;
}
// Handle Map
if (obj instanceof Map) {
const cloned = new Map();
visited.set(obj, cloned);
obj.forEach((value, key) => {
cloned.set(cloneDeep(key, visited), cloneDeep(value, visited));
});
return cloned;
}
// Handle Set
if (obj instanceof Set) {
const cloned = new Set();
visited.set(obj, cloned);
obj.forEach(value => {
cloned.add(cloneDeep(value, visited));
});
return cloned;
}
// Handle Array
if (Array.isArray(obj)) {
const cloned = [];
visited.set(obj, cloned);
obj.forEach((item, index) => {
cloned[index] = cloneDeep(item, visited);
});
return cloned;
}
// Handle Object
const cloned = {};
visited.set(obj, cloned);
// Copy own properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = cloneDeep(obj[key], visited);
}
}
// Copy symbol properties
const symbols = Object.getOwnPropertySymbols(obj);
symbols.forEach(symbol => {
cloned[symbol] = cloneDeep(obj[symbol], visited);
});
return cloned;
}
Alternative Methods
Using JSON (Limited)
function cloneDeepJSON(obj) {
return JSON.parse(JSON.stringify(obj));
}
// Limitations:
// - Doesn't handle functions
// - Doesn't handle undefined
// - Doesn't handle symbols
// - Doesn't handle circular references
// - Doesn't handle Date, RegExp, Map, Set
Using Structured Clone (Modern)
function cloneDeepStructured(obj) {
return structuredClone(obj);
}
// Browser support: Modern browsers only
// Handles: Most types including Date, RegExp, Map, Set
// Doesn't handle: Functions, DOM nodes, some built-in objects
Use Cases
1. State Management
const currentState = {
user: { name: "John", preferences: { theme: "dark" } },
items: [{ id: 1, name: "Item 1" }]
};
// Create immutable update
const newState = cloneDeep(currentState);
newState.user.preferences.theme = "light";
// currentState remains unchanged
2. Configuration Objects
const defaultConfig = {
api: { baseUrl: "https://api.example.com", timeout: 5000 },
ui: { theme: "light", language: "en" }
};
// Create user-specific config
const userConfig = cloneDeep(defaultConfig);
userConfig.ui.theme = "dark";
3. Data Transformation
const originalData = {
users: [
{ id: 1, name: "John", metadata: { role: "admin" } }
]
};
// Transform without mutating original
const transformed = cloneDeep(originalData);
transformed.users.forEach(user => {
user.displayName = user.name.toUpperCase();
});
Performance Considerations
Optimized Version
function cloneDeepFast(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== "object") return obj;
if (cache.has(obj)) return cache.get(obj);
let cloned;
if (Array.isArray(obj)) {
cloned = [];
cache.set(obj, cloned);
for (let i = 0; i < obj.length; i++) {
cloned[i] = cloneDeepFast(obj[i], cache);
}
} else {
cloned = {};
cache.set(obj, cloned);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = cloneDeepFast(obj[key], cache);
}
}
}
return cloned;
}
Comparison with Shallow Clone
// Shallow clone
const shallow = Object.assign({}, obj);
// or
const shallow = { ...obj };
// Deep clone
const deep = cloneDeep(obj);
// Example
const original = { a: { b: 1 } };
const shallowCopy = { ...original };
const deepCopy = cloneDeep(original);
shallowCopy.a.b = 2;
console.log(original.a.b); // 2 (changed!)
deepCopy.a.b = 3;
console.log(original.a.b); // 2 (unchanged!)
Best Practices
- Use for Complex Objects: When you need complete independence
- Handle Circular References: Use WeakMap to track visited objects
- Consider Performance: Deep cloning can be expensive for large objects
- Type Safety: Handle all data types you expect
- Memory Management: Be aware of memory usage for large clones
- Use Structured Clone: When available, it's faster and handles more types
Common Pitfalls
Pitfall 1: Functions Not Cloned
// Functions are not cloned, they're referenced
const obj = { fn: () => console.log("test") };
const cloned = cloneDeep(obj);
cloned.fn === obj.fn; // true (same reference)
Pitfall 2: Prototype Chain
// Prototype chain is not preserved
class MyClass {}
const obj = new MyClass();
const cloned = cloneDeep(obj);
cloned instanceof MyClass; // false
Pitfall 3: Non-enumerable Properties
// Only enumerable properties are cloned
const obj = {};
Object.defineProperty(obj, "hidden", {
value: "secret",
enumerable: false
});
const cloned = cloneDeep(obj);
cloned.hidden; // undefined
Real-World Example
class StateManager {
constructor(initialState) {
this.state = cloneDeep(initialState);
this.history = [cloneDeep(initialState)];
}
updateState(updater) {
const newState = cloneDeep(this.state);
updater(newState);
this.state = newState;
this.history.push(cloneDeep(newState));
}
undo() {
if (this.history.length > 1) {
this.history.pop();
this.state = cloneDeep(this.history[this.history.length - 1]);
}
}
}