Deep‑Cloning vs. Shallow‑Cloning in JavaScript

When working with objects in JavaScript, we often need to create copies. However, not all copies behave the same way. Some methods copy only the top‑level properties, whilst others create a fully independent duplicate. If we do not use the right approach, we can run into unintended side effects where changing one object also changes another.
This is where shallow cloning and deep cloning come into play. In this article, I explore how they work, the best methods for each, and when to use them to avoid unnecessary bugs.
What is Shallow‑Cloning?
A shallow clone creates a new object with copies of the original's top‑level properties. However, if the object contains nested objects or arrays, those remain referenced rather than copied. This means changes to nested structures affect both the original and the clone.
An Example of Shallow‑Cloning
The spread operator (...) is a common way to create a shallow clone of an object:
const original = { name: "Ellie", details: { age: 30 } };const shallowClone = { ...original };shallowClone.name = "Bob";shallowClone.details.age = 40;console.log(original.name); // "Ellie" (unchanged)console.log(original.details.age); // 40 (changed)Here, shallowClone.name is independent of the original; however, shallowClone.details still points to the same object as original.details. This means modifying shallowClone.details.age also updates original.details.age.
Other Ways to Create a Shallow Clone
Apart from the spread operator, we can also do something like this:
const shallowClone1 = Object.assign({}, original);// structuredClone creates a deep clone; transfer controls transferable ownershipObject.assign() behaves like the spread example here, copying the first level whilst retaining references to nested objects. structuredClone is a deep‑cloning operation and is covered below.
When Should We Use Shallow Cloning?
Shallow cloning is ideal when:
- The object only contains primitive values.
- We need a quick copy without duplicating deeply nested structures.
- The nested objects should remain shared between copies.
If we need a completely separate object, including its nested properties, we need a deep clone.
What is Deep‑Cloning?
A deep clone creates a fully independent copy of an object, including all nested objects and arrays. Changes to the cloned object do not affect the original, and vice versa.
An Example of Deep‑Cloning
const original = { name: "Ellie", details: { age: 30 } };const deepClone = JSON.parse(JSON.stringify(original));deepClone.details.age = 40;console.log(original.details.age); // 30 (unchanged)Since we have created a new structure, modifying deepClone.details.age does not affect original.details.age.
Better Ways to Deep‑Clone Objects
Whilst JSON.parse(JSON.stringify(...)) works, it has limitations:
- Dates become strings, Map and Set entries are lost, and
BigIntvalues cause an exception. - Functions,
undefinedand symbols are omitted from objects or becomenullin arrays.
Choose a cloning mechanism by the value types and semantics required. structuredClone supports the structured‑clone types, whilst a utility such as Lodash's cloneDeep has its own compatibility and type‑handling trade‑offs.
Using structuredClone (Modern JavaScript Method)
const deepClone = structuredClone(original);This method creates a deep copy of supported structured‑clone types. It throws a DataCloneError for functions and other unsupported values. A transfer list is a separate option: it transfers supported resources to the clone and detaches them from the source rather than making a second usable copy.
Using Lodash's cloneDeep
In situations where you might need to support browsers that pre‑date structuredClone, Lodash offers an easy‑to‑use method to achieve much the same:
import cloneDeep from "lodash.clonedeep";const deepClone = cloneDeep(original);Lodash's cloneDeep recursively copies many common object and collection structures. It is a compatibility choice, not a universally faithful clone: confirm the required behaviour for functions, custom prototypes and host objects. The same caution applies when choosing between it, JSON.parse(JSON.stringify(obj)) and structuredClone.
Performance Considerations
Deep cloning is more computationally expensive than shallow cloning, as it creates entirely new copies of nested structures. If an object is large or deeply nested, this can lead to significant performance overhead.
Shallow vs. Deep Cloning Performance
We can see the performance differences very easily by setting up a simple test:
const obj = { nested: { value: 42 } };console.time("Shallow Clone");for (let i = 0; i < 1000000; i++) { const clone = { ...obj };}console.timeEnd("Shallow Clone");console.time("Deep Clone");for (let i = 0; i < 1000000; i++) { const clone = structuredClone(obj);}console.timeEnd("Deep Clone");For small objects, the performance difference is minor. However, deep cloning can become significantly slower as objects grow larger.
When Should We Use Deep Cloning?
Deep cloning is useful when:
- We need completely independent objects to avoid unwanted mutations.
- The object contains nested structures that should not be shared.
- We are working with immutable state updates in libraries like Redux.
For simple structures or when performance is a concern, shallow cloning is often a better choice.
Wrapping Up
Choosing between shallow and deep cloning depends on what we are trying to achieve. If we only need a copy of top‑level properties, shallow cloning is fast and efficient. However, if we need to ensure that nested structures are fully independent, deep cloning is the safer approach.
Key Takeaways
Shallow cloning
copies top‑level properties but keeps references to nested objects.Deep cloning
creates a fully independent copy of an object, including its nested structures.The spread operator
(...) andObject.assign()perform shallow cloning.
and Lodash'sstructuredClone()cloneDeep()are deep‑cloning options with different supported types; neither clones every JavaScript value.Deep cloning has performance costs
, so it should only be used when necessary.
Understanding how these cloning methods work helps us write more predictable and bug‑free code, making our applications easier to maintain.