Deep‑Cloning vs. Shallow‑Cloning in JavaScript

Image by Philipp Katzenberger.

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 toplevel 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 toplevel 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 ownership

Object.assign() behaves like the spread example here, copying the first level whilst retaining references to nested objects. structuredClone is a deepcloning 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 BigInt values cause an exception.
  • Functions, undefined and symbols are omitted from objects or become null in arrays.

Choose a cloning mechanism by the value types and semantics required. structuredClone supports the structuredclone types, whilst a utility such as Lodash's cloneDeep has its own compatibility and typehandling tradeoffs.

Using structuredClone (Modern JavaScript Method)

const deepClone = structuredClone(original);

This method creates a deep copy of supported structuredclone 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 predate structuredClone, Lodash offers an easytouse 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 toplevel 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 toplevel properties but keeps references to nested objects.
  • Deep cloning

    creates a fully independent copy of an object, including its nested structures.
  • The spread operator

    (...) and Object.assign() perform shallow cloning.
  • structuredClone()

    and Lodash's cloneDeep() are deepcloning 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 bugfree code, making our applications easier to maintain.


Want to find out more?

If you need senior handson support with a complex React or Next.js platform, migration, performance issue, or technical SEO problem, send me the context and I'll tell you where I can help.