Example: Shallow Copy vs. Deep Copy
import cloneDeep from 'lodash/cloneDeep';
const original = [
{ id: 1, info: { status: "active" } },
{ id: 2, info: { status: "inactive" } }
];
// Shallow copy
const shallowCopy = original.slice();
shallowCopy[0].info.status = "changed";
console.log(original[0].info.status); // "changed" (still linked)
// Deep copy
const deepCopy = cloneDeep(original);
deepCopy[0].info.status = "changed again";
console.log(original[0].info.status); // "changed" (original unaffected)
console.log(deepCopy[0].info.status); // "changed again"
cloneDeep from Lodash recursively walks through the structure of your object or array and:
- Creates new objects/arrays instead of reusing existing ones.
- Copies primitive values (string, number, boolean) directly.
- Handles special types (Date, Map, Set, RegExp, Buffer, TypedArrays).
- Supports circular references (won’t get stuck in infinite recursion).
- Preserves property descriptors, prototype chains, and symbols.
The Core Problem It Solves
In JavaScript, objects and arrays are reference types.
const a = { name: "Ola" };
const b = a; // just a reference, not a copy
b.name = "Changed";
console.log(a.name); // Changed 😱
Here, a and b are pointing to the same object in memory.
If you try to "copy" an array of objects using things like .slice() or .filter(), you only copy the outer array, not the inner objects.
So you still have shared references to the nested objects.
When You Should Use cloneDeep:
- Transformations: You’re modifying data but want the original untouched.
- Redux state: Avoid accidental mutation of state objects.
- Test data: Copy fixtures so each test gets a fresh, isolated version.
- Breaking shared references: When two lists should contain separate but identical-looking objects.
When You Should Use a Shallow Copy:
- The data does not contain nested objects/arrays that we care about mutating independently.
Or when we only need to change the outer layer of the structure.
Arrays of primitives
const nums = [1, 2, 3];
const copy = nums.slice(); // shallow copy
copy[0] = 99;
console.log(nums); // [1, 2, 3] ✅ unaffected
Primitives (numbers, strings, booleans) are copied by value,
so shared references aren’t a problem._"Copied by value" means_When you assign or copy a primitive, JavaScript copies the actual value, not a reference to it.
So changes to one variable do not affect the other.
- Short-lived transformations If you just need a quick copy to sort, filter, or reverse the outer array but don’t care about mutating the nested items later:
const people = [{ name: "Ola" }, { name: "Ali" }];
// Just sorting names
const sorted = [...people].sort((a, b) => a.name.localeCompare(b.name));
console.log(sorted[0] === people[0]); // true (same reference inside)
Sorting changes only the array order, not the inner objects.
- Replacing the top-level object
const state = { count: 0, user: { name: "Ola" } };
// Only replacing user object
const newState = { ...state, user: { name: "John" } };
console.log(state.user.name); // Ola ✅ unchanged
console.log(newState.user.name); // John
Here, we create a new outer object but explicitly replace the nested one.
No need to deep clone the whole thing.
💡Rule of Thumb:
If the structure has no nested objects/arrays that will be mutated independently,
or if you will replace nested data entirely instead of mutating it,
then a shallow copy is fine.
Top comments (0)