Deep Cloning Objects in JavaScript, the Modern Way

Deep Cloning Objects in JavaScript, the Modern Way

Deep Cloning Objects in JavaScript, the Modern Way

May 12, 2025

Have you ever needed to make a complete copy of a JavaScript object, one where if you change something inside the copy, the original object stays exactly the same? This task, known as "deep cloning" or "deep copying," has historically been tricky in JavaScript, especially when objects contain other objects, arrays, or special types of data nested inside them. Developers often had to rely on less-than-perfect workarounds or external libraries to achieve a truly independent copy.

But there's good news! Did you know there's now a native way built right into JavaScript to handle deep copies reliably?

That's right, a powerful function called structuredClone is now part of the standard JavaScript environment you use. This means you no longer necessarily need complex code or extra tools just to make a full copy of an object.

Here’s a simple example:

JavaScript

const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}

// The modern way to copy deeply

const copied = structuredClone(calendarEvent)

In the example above, notice that not only was the main object copied, but also the array nested inside it (attendees) and even the Date object (date).

Everything works exactly as you would expect:

JavaScript

copied.attendees // ["Steve"]
copied.date // Date: Wed Dec 31 1969 16:00:00
calendarEvent.attendees === copied.attendees // false (They are distinct arrays)

The last check (===) shows that the attendees array in the original object is not the same array in the copied object; it's a completely separate copy.

That's right, structuredClone can do all of the above. Plus, it can:

  • Clone objects and arrays that are nested inside each other to any level.

  • Clone objects that have circular references (where properties link back to the original object or earlier parts of the structure).

  • Clone a wide range of JavaScript types, including Date, Set, Map, Error types, RegExp, ArrayBuffer, Blob, File, ImageData, and many others.

  • Transfer any transferable objects (like ArrayBuffer).

So, even a complex object like this would be copied correctly:

JavaScript

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [ new File([], 'file.txt') ] }, // Using empty array for BlobData example
  error: new Error('Hello!')
}

// Create a circular reference

kitchenSink.circular = kitchenSink

// All good, fully and deeply copied!

const clonedSink = structuredClone(kitchenSink)

Why Not Just Object Spread (...)?

It’s important to remember that structuredClone performs a deep copy. If you only need a shallow copy (a copy where nested objects or arrays are not copied, but instead the new object just holds references to the same nested items), then the object spread syntax works fine:

JavaScript

const simpleEvent = {
  title: "Builder.io Conf",
}

//No problem, there are no nested objects or arrays

const shallowCopy = {...simpleEvent}

Other ways to do shallow copies include Object.assign({}, simpleEvent) or Object.create(simpleEvent).

But, if your object has nested items, using shallow copy methods leads to problems:

JavaScript

const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}
const shallowCopy = {...calendarEvent}

// Oops - adding "Bob" to the shallow copy also adds him to the original object!

shallowCopy.attendees.push("Bob")

// Oops - changing the date in the shallow copy also changes it in the original object!

shallowCopy.date.setTime(456)

As you can see, the shallow copy did not create new copies of the nested date object and attendees array. They are still linked to the original object. If you try to change these nested items through the shallow copy, you will unexpectedly change the original object as well, which can cause major issues.

Why Not JSON.parse(JSON.stringify(x))?

Ah yes, this common technique. It can be quite fast, but it has some important limitations that structuredClone handles better.

Let's use the calendarEvent example again:

JavaScript

const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}

// Using JSON methods to copy

const problematicCopy = JSON.parse(JSON.stringify(calendarEvent))

If you look at problematicCopy, you would see:

JavaScript

{
  title: "Builder.io Conf",
  date: "1970-01-01T00:00:00.123Z", // The Date object is gone!
  attendees: ["Steve"]
}

This is not what was intended! The date property should be a Date object, not a string.

This happens because JSON.stringify can only correctly handle basic objects, arrays, and simple data types (like strings, numbers, booleans, null). Other types are handled in ways that might be hard to predict. For example, Date objects are turned into strings. A Set or Map is simply converted into an empty plain object ({}).

JSON.stringify also completely ignores certain values, such as undefined or functions.

If we tried to copy our kitchenSink example using this method:

JavaScript

const kitchenSink = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [ new File([], 'file.txt') ] }, // Using empty array for BlobData example
  error: new Error('Hello!')
}

// Trying to copy with JSON methods

const veryProblematicCopy = JSON.parse(JSON.stringify(kitchenSink))

The result would be:

JavaScript

{
  "set": {}, // Set became an empty object
  "map": {}, // Map became an empty object
  "regex": {}, // RegExp became an empty object
  "deep": {
    "array": [
      {} // File became an empty object
    ]
  },
  "error": {}, // Error became an empty object
}

This doesn't give us the correct types!

Also, you would have to remove any circular references before using JSON.stringify, as it throws errors if it finds one.

So, while the JSON.parse(JSON.stringify(x)) method can be useful for simple cases, structuredClone can handle many types and circular references that this method cannot.

Why Not _.cloneDeep from Lodash?

Before structuredClone, Lodash's cloneDeep function was a very common way to solve the deep cloning problem.

And indeed, using cloneDeep from Lodash works as expected:

JavaScript

import cloneDeep from 'lodash/cloneDeep'
const calendarEvent = {
  title: "Builder.io Conf",
  date: new Date(123),
  attendees: ["Steve"]
}
const clonedEvent = cloneDeep(calendarEvent)

// This would work correctly:

// clonedEvent.attendees !== calendarEvent.attendees

// clonedEvent.date !== calendarEvent.date

However, there is one drawback here. According to the text, importing just this one function from Lodash can add a notable amount of code to your project (17.4kb minified or 5.3kb gzipped). If you don't import it in the most efficient way, you could accidentally import even more (up to 25kb).

While adding a few kilobytes might not break your project, it's not necessary anymore since browsers already have structuredClone built-in, meaning you don't need to add any extra code for basic deep cloning.

What structuredClone Cannot Clone

There are some types of JavaScript values that structuredClone cannot copy:

  • Functions: Trying to clone a function will cause an error called DataCloneError.


  • JavaScript

// Error!

structuredClone({ fn: () => { } })
  • DOM nodes: Elements from a web page (like document.body) also cannot be cloned and will throw a DataCloneError.


  • JavaScript

// Error!

structuredClone({ el: document.body })
  • Property descriptors, setters, and getters: Information about how properties work, like getter and setter functions, is not cloned. Only the resulting value of the property is copied.


  • JavaScript

// Has a getter function

const objWithGetter = { get foo() { return 'bar' } }
const cloned = structuredClone(objWithGetter)

// Becomes: { foo: 'bar' } - The getter is gone, only the value is copied.

  • Object prototypes: The chain of objects that determines inheritance is not copied. If you clone an object that was created from a class (instanceof MyClass), the cloned object will have the same properties and values, but it will no longer be recognized as an instance of that specific class.


  • JavaScript

class MyClass {
   foo = 'bar'
   myMethod() { /* ... */ }
}
const myClass = new MyClass()
const cloned = structuredClone(myClass)

// Becomes: { foo: 'bar' } - The method is not copied, and the link to the class is lost.

cloned instanceof MyClass // false

Full List of Supported Types

To put it simply, structuredClone can clone anything included in the list below. Anything not on this list cannot be cloned.

JS Built-ins:

Array, ArrayBuffer, Boolean, DataView, Date, Error types (listed below), Map, Object (but only plain objects created like {}), Primitive types (except symbol), RegExp, Set, TypedArray

*Primitive types included are: number, string, null, undefined, boolean, BigInt.

Error types:

Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError

Web/API types:

AudioData, Blob, CryptoKey, DOMException, DOMMatrix, DOMMatrixReadOnly, DOMPoint, DomQuad, DomRect, File, FileList, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, ImageBitmap, ImageData, RTCCertificate, VideoFrame

Browser and Runtime Support

And the good news is, structuredClone is supported in all major web browsers, and also in environments like Node.js and Deno.

The text notes there is a caveat (a minor limitation) with support in Web Workers.

Conclusion

Deeply copying objects is a necessary task sometimes, especially when you need to change a copy without affecting the original. While methods like object spread are fine for shallow copies and JSON.parse(JSON.stringify(x)) can work for simple cases, they have limitations. Using an external library function like Lodash's cloneDeep adds extra code to your project.

The native structuredClone function provides a built-in, powerful, and safe way to perform deep copies of a wide variety of JavaScript types, including complex structures and circular references. Since it's supported in modern environments, it's the recommended modern way to handle deep cloning in JavaScript.

Accelerate Your UI Build Process

Mastering JavaScript's native features like structuredClone is key to writing robust and efficient code. But while you're optimizing your data handling, are you also optimizing how you turn designs into functional user interfaces? Manually translating complex designs from Figma into code can still be a time-consuming step in the development process.

If you're looking for ways to dramatically speed up your front-end development workflow, explore tools designed to bridge the gap between design and code. Superflex helps you go from Figma designs to functional code in seconds, freeing up valuable developer time to focus on logic, performance, and advanced features (like mastering deep cloning!). Discover how you can build user interfaces faster and more efficiently.

Learn more and start at Superflex.ai.