When Copying Isnât Copying: Rethinking Deep and Shallow Copy in JavaScript
Deep copy creates a fully independent duplicate of an object including all nested data, while shallow copy only duplicates the top-level structure and keeps nested references shared, making the choice between them a matter of intentional data design rather than safety alone.
Thereâs a moment every developer runs into.
You âcopyâ an object.
You change the copy.
And somehow⌠the original changes too.
It feels like a bug.
But itâs not.
Itâs a misunderstanding.
Because in JavaScript, copying is not always what it seems.
The Illusion of Copying
With simple values, everything behaves as expected.
const a = 10
const b = aYou change b, and a remains untouched.
Because primitives are copied by value.
But objects are different.
const a = { name: "Rishi" }
const b = aNow both variables point to the same thing.
Not two objects.
One.
So when b changes, a changes too.
The copy never really happened.
Shallow Copy: A Surface-Level Duplicate
To solve this, JavaScript gives you a way to copy objects:
const b = { ...a }At first glance, this feels correct.
You now have two objects.
But only at the surface.
If the object contains nested data:
const a = {
name: "Rishi",
address: { city: "Jakarta" }
}
const b = { ...a }Then something subtle remains.
The nested object is still shared.
b.address.city = "Bandung"And suddenly:
a.address.city // "Bandung"The copy was only partial.
The structure was duplicatedâ
but the deeper parts remained connected.
Deep Copy: Breaking the Connection
A deep copy goes further.
It recreates everything.
const b = structuredClone(a)Now, even nested objects are independent.
Changes in one no longer affect the other.
The connection is completely broken.
What was once shared is now isolated.
The Hidden Trade-Off
At this point, itâs tempting to think:
âDeep copy is safer. I should always use it.â
But this is where things become more nuanced.
Because deep copy doesnât just remove bugsâ
it removes relationships.
When Sharing Is Intentional
Sometimes, you want parts of your data to stay connected.
const state = {
user: { name: "Rishi" },
settings: { theme: "dark" }
}
const newState = {
...state,
user: { ...state.user, name: "Indana" }
}Here, only the user changes.
settings remains shared.
This is not a mistake.
Itâs intentional.
And it introduces an important idea:
Not everything needs to be copied
Structural Sharing
This patternâcopying only what changesâis known as structural sharing.
Instead of duplicating everything, you preserve what stays the same.
- memory usage stays low
- comparisons become faster
- updates remain predictable
This is why many frameworks rely on shallow copying.
Not because itâs simplerâ
but because itâs more efficient.
The Real Problem
The bugs youâve experienced donât come from shallow copy itself.
They come from unclear boundaries.
When you donât know which parts are sharedâ
and which are independentâ
unexpected changes appear.
The issue is not the tool.
Itâs the intent behind it.
A Different Way to Think About Copying
Instead of asking:
âShould I use shallow or deep copy?â
You begin to ask:
âWhat should remain shared, and what should be isolated?â
That question changes everything.
Because copying is no longer mechanical.
It becomes a design decision.
A Final Thought
Deep copy gives you isolation.
Shallow copy preserves connection.
Neither is inherently better.
They simply express different intentions.
And once you understand that,
you stop copying blindlyâ
and start shaping how your data lives and evolves.