How to Deep Clone Objects in JavaScript: structuredClone vs JSON vs Libraries
javascriptobjectsstructuredClonetutorialutilities

How to Deep Clone Objects in JavaScript: structuredClone vs JSON vs Libraries

CCode Compass Editorial
2026-06-14
11 min read

A practical comparison of structuredClone, JSON cloning, and library helpers for deep cloning objects in JavaScript.

Deep cloning looks simple until real application data gets involved. A shallow copy can leak mutations across components, a JSON round-trip can silently destroy types, and a utility library can solve the problem at the cost of extra bundle weight or behavior you did not expect. This guide compares the main deep clone strategies in JavaScript—structuredClone, JSON.parse(JSON.stringify(...)), and library-based approaches—so you can choose the right option for browser apps, Node.js code, state updates, worker messages, tests, and utility-heavy codebases.

Overview

If you need to clone object graphs in JavaScript, there is no single best answer for every project. The right choice depends on the kinds of values you need to preserve, where the code runs, and whether you care more about correctness, speed, compatibility, or bundle size.

At a high level, the three most common approaches are:

  • structuredClone(): the modern built-in API designed to clone many native JavaScript and web platform types correctly.
  • JSON.parse(JSON.stringify(value)): a compact and widely known technique that works only for plain JSON-safe data.
  • Library helpers: utilities such as deep clone functions from general-purpose libraries or specialized packages, useful when you need custom behavior, older-environment support, or a smaller targeted abstraction.

For many current applications, structuredClone is the first option worth checking because it is built into the platform and handles much more than JSON can. But that does not mean it is always the best fit. If your data is strictly simple and serializable, the JSON approach may still be enough in internal tooling or throwaway transforms. And if you need consistent behavior across older runtimes or want more control over special cases, a library can still be the most practical choice.

Before going deeper, it helps to separate three related ideas that often get mixed together:

  • Assignment: const b = a copies the reference, not the object.
  • Shallow copy: {...a} or Object.assign({}, a) copies only the top level.
  • Deep clone: nested arrays and objects are copied recursively into a new structure.

Example:

const original = {
  user: { name: 'Mina' },
  tags: ['js', 'clone']
};

const shallow = { ...original };
shallow.user.name = 'Sam';

console.log(original.user.name); // 'Sam'

The top-level object changed, but the nested user object was still shared. Deep cloning exists to avoid that kind of accidental coupling.

How to compare options

The fastest way to pick a deep clone strategy is to compare them against the actual shape of your data, not against generic advice. Here are the criteria that matter most in practice.

1. What kinds of values do you need to preserve?

This is usually the deciding factor. Ask whether your objects may contain:

  • Plain objects and arrays
  • Dates
  • Maps and Sets
  • Typed arrays or ArrayBuffers
  • RegExp objects
  • Circular references
  • Undefined values
  • Functions
  • Class instances
  • Errors, Blobs, Files, or other platform-specific objects

If your data is truly plain JSON-shaped data, more options are available. If your values include richer types, the JSON trick stops being reliable quickly.

2. Does your runtime support structuredClone?

Because structuredClone is a built-in API, environment support matters. In current modern browsers and modern Node.js environments, it is commonly available, but compatibility is still something to verify in the exact versions you support. If your app targets older environments, a library or fallback path may still be necessary. This is the same kind of version-awareness that matters in broader tooling choices, such as package manager and Node feature decisions; if version boundaries are part of your workflow, a reference like the Node.js Version Compatibility Guide: ESM, TypeScript, Fetch, and Test Runner Support can help you think about support policy more systematically.

3. How important is bundle size?

A built-in API adds no package dependency. A library may add kilobytes you do not need, especially if all you wanted was one deep clone helper. In frontend code, that can be enough reason to prefer a native option. In backend code or internal tooling, the tradeoff may be less important.

4. Do you need custom semantics?

Sometimes “deep clone” is not the real requirement. You may actually need one of these:

  • A clone that preserves prototypes
  • A clone that skips certain keys
  • A clone that transforms values during copy
  • A clone that tolerates unsupported values instead of throwing
  • A clone only for plain data structures used in state management

Built-ins are opinionated. Libraries can be more flexible, but they also vary more in edge-case behavior.

5. Are you cloning too much?

This is an important design check. Deep cloning is sometimes used as a blanket fix for state mutation problems, but it can be wasteful. If you only need to update one nested branch, a targeted immutable update is usually better than cloning the entire structure. In React or other UI architectures, that distinction matters. If your application already relies on a state management library, review whether the library encourages immutable updates, draft-based updates, or normalized state rather than full deep copies. That is one reason articles like Best JavaScript State Management Libraries Compared for React and Beyond are useful alongside low-level cloning advice.

6. Do you need cloning for transport, not just copying?

Some workflows involve worker threads, message passing, persistence, or API boundaries. In those cases, clone behavior often overlaps with serialization rules. If your data is going over the network, JSON constraints may be acceptable or even desirable. If it stays in memory, preserving richer types may matter more.

Feature-by-feature breakdown

This section compares the main strategies where they differ most: supported types, safety, ergonomics, and practical fit.

structuredClone()

structuredClone is the native deep clone API and the cleanest default for many modern JavaScript projects.

Why it stands out:

  • Handles many built-in types beyond plain objects and arrays
  • Supports circular references
  • Avoids the data loss common with JSON stringification
  • Requires no dependency

Basic usage:

const original = {
  user: { name: 'Mina' },
  createdAt: new Date(),
  meta: new Map([['role', 'admin']])
};

const copy = structuredClone(original);

console.log(copy !== original); // true
console.log(copy.user !== original.user); // true

What it is good at:

  • Plain objects and arrays
  • Nested data
  • Date, Map, Set, RegExp, and many binary data types
  • Circular object graphs
  • Codebases that prefer native APIs over dependencies

What to watch for:

  • Functions are not generally cloneable with the structured clone algorithm
  • Some custom class behavior may not survive in the way you expect
  • Environment support must still be verified for your target matrix
  • It may throw on unsupported values instead of quietly degrading

That last point is often a benefit, not a drawback. Failing loudly is usually better than silently flattening your data into something different.

JSON.parse(JSON.stringify(value))

This is the familiar fallback because it is short and easy to remember. It can work, but only when your data is already valid JSON in spirit as well as in syntax.

const copy = JSON.parse(JSON.stringify(original));

What it is good at:

  • Very simple data: plain objects, arrays, strings, numbers, booleans, and null
  • Quick scripts and one-off transforms
  • Situations where JSON-safe output is acceptable

What it breaks or changes:

  • Date becomes a string
  • undefined is dropped or altered depending on context
  • Map and Set do not survive meaningfully
  • RegExp loses behavior
  • Functions are omitted
  • Circular references throw
  • Prototype chains and class instances are not preserved

Example:

const original = {
  now: new Date(),
  missing: undefined,
  pattern: /abc/g
};

const copy = JSON.parse(JSON.stringify(original));

console.log(copy);
// now is a string, missing may disappear, pattern becomes an empty object or loses useful form

The main risk with the JSON approach is not that it fails loudly. It often fails quietly. That makes it dangerous in long-lived application code because bugs can show up much later than the clone operation itself.

A useful rule: if you would not be comfortable sending the object through a JSON API and reconstructing it manually, do not use this as your deep clone strategy.

Library-based deep clone helpers

Library solutions sit between native convenience and custom control. You might use a standalone package, a helper from a utility library, or a domain-specific clone function built for your application's data model.

Why developers still choose libraries:

  • Need compatibility with older environments
  • Want behavior different from structuredClone
  • Already depend on a utility library and prefer consistency
  • Need a known abstraction in shared code used across projects

Potential advantages:

  • Can be easy to drop into existing code
  • May offer predictable behavior across runtimes
  • Some libraries target plain objects efficiently
  • Useful when native support is uncertain

Tradeoffs:

  • Extra dependency to maintain
  • Possible bundle cost
  • Behavior differs from package to package
  • May not support every special type you expect
  • Can encourage overuse of deep cloning where a narrower operation would be better

If you are evaluating a “best deep clone library js” option, compare it on exact inputs that matter to you rather than on popularity alone. Create a short test matrix with your real data shapes: nested arrays, dates, maps, circular refs, class instances, and any binary structures you use. The winning library is the one that behaves correctly in your context with acceptable size and maintenance overhead.

Quick comparison summary

  • Use structuredClone when you want a modern built-in that handles rich data types more safely than JSON.
  • Use JSON round-tripping only when the data is truly plain and JSON-safe, and losing type information is acceptable.
  • Use a library when compatibility, custom semantics, or project constraints make the native API less practical.

Common mistakes to avoid

  • Using deep cloning to patch around mutation-heavy design instead of fixing update logic
  • Assuming spread syntax is a deep copy
  • Assuming JSON cloning is “good enough” for Dates, Maps, or class instances
  • Adding a library dependency before checking whether structuredClone already solves the problem
  • Benchmarking clone speed before confirming clone correctness

Best fit by scenario

If you want a practical answer fast, start here.

Scenario: Modern browser app or current Node.js service

Best fit: structuredClone

If your supported environments include the API and your data contains more than plain JSON values, this is usually the strongest default. It keeps the code readable and removes the need for an extra package.

Scenario: Quick utility script with plain API payloads

Best fit: JSON round-trip, if the data is strictly JSON-safe

For temporary scripts, build steps, or simple transforms where everything is already JSON data, the older pattern can still be acceptable. Just be explicit in comments so future readers do not treat it as a general-purpose clone utility.

Scenario: Frontend bundle where dependency count matters

Best fit: Prefer structuredClone, avoid pulling a large utility package just for cloning

This is especially true in performance-sensitive apps where every dependency gets reviewed. Similar thinking applies when comparing other frontend choices, whether you are evaluating HTTP clients, animation libraries, or UI stacks. If that is part of your workflow, related comparisons such as JavaScript Fetch vs Axios vs ky: Which HTTP Client Should You Use? and CSS Frameworks and UI Libraries for JavaScript Apps Compared can help reinforce the same decision discipline: native or minimal first, dependency only when the extra behavior is worth it.

Scenario: Legacy environment support

Best fit: Library or a guarded fallback strategy

If your runtime support policy includes environments that may not have structuredClone, a library can provide a consistent abstraction. Another practical pattern is a wrapper utility that uses structuredClone when available and falls back to a narrower alternative only for supported data shapes.

function deepClone(value) {
  if (typeof structuredClone === 'function') {
    return structuredClone(value);
  }

  // Only safe if your application guarantees JSON-shaped data
  return JSON.parse(JSON.stringify(value));
}

This wrapper is useful only if you are honest about the fallback limitations. If your application data includes richer types, use a library fallback instead of JSON.

Scenario: Application state updates

Best fit: Usually not full deep cloning

For reducers, stores, and UI state transitions, cloning the whole tree is often too blunt. Prefer targeted updates, immutable helpers, or state libraries designed to reduce accidental mutation. Deep clone can be appropriate for snapshots, reset logic, or test fixtures, but not as a universal update strategy.

Scenario: Test fixtures and isolated object copies

Best fit: structuredClone or a small project utility

Tests often need fresh copies of nested data to avoid cross-test contamination. Native cloning works well here as long as the data types are supported and your test runtime includes the API.

When to revisit

Your deep clone choice should not be permanent. Revisit it when the assumptions behind the decision change.

Review this topic when:

  • Your browser or Node support matrix changes
  • You introduce richer data types such as Maps, Sets, or typed arrays
  • You start using workers, message passing, or cross-context data transfer
  • You notice deep clone calls in hot paths or large render/update loops
  • You add a utility library and want to reduce duplicate abstractions
  • A package you rely on changes its behavior, maintenance status, or footprint
  • New built-in platform behavior makes an old workaround unnecessary

A practical maintenance step is to centralize cloning behind one small utility instead of scattering implementation details across the codebase. That gives you one place to swap behavior later.

export function cloneData(value) {
  return structuredClone(value);
}

Even if that looks thin today, it creates a clean decision boundary. If support requirements, library choices, or data shapes change later, you update one function rather than dozens of call sites.

It is also worth documenting the intended use in plain language:

  • What kinds of values the app expects to clone
  • Whether functions or class instances are allowed
  • Which runtime versions are assumed
  • Whether this helper is for state, tests, transport, or general utilities

That kind of discipline pays off across a developer tooling stack. The same “use one clear abstraction and revisit when platform support changes” mindset applies to many recurring choices in JavaScript projects, from monorepo tooling to package managers. If you are standardizing broader team practices, articles like JavaScript Monorepo Tools Compared: Turborepo vs Nx vs pnpm Workspaces and Best JavaScript Package Managers Compared: npm vs pnpm vs Yarn vs Bun are useful next reads.

A simple final recommendation:

  1. Start with structuredClone if your supported environments allow it.
  2. Use JSON cloning only for intentionally plain JSON-shaped data.
  3. Reach for a library when compatibility or custom behavior makes it necessary.
  4. Do not use deep cloning where a narrower immutable update would be better.
  5. Hide the choice behind a small utility so you can revise it later.

That approach is durable, easy to maintain, and much less likely to surprise you six months from now.

Related Topics

#javascript#objects#structuredClone#tutorial#utilities
C

Code Compass Editorial

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-14T09:05:54.057Z