Clone

Master JavaScript object cloning with structuredClone() - the modern native solution for deep copying complex data structures.

Understanding Object Cloning in JavaScript

When you assign an object to a new variable in JavaScript, you're not creating an independent copy--instead, both variables reference the same underlying object in memory. This reference-based behavior can lead to unexpected mutations when you modify what appears to be a separate copy.

The challenge of cloning becomes more apparent with nested data structures. An object that contains other objects, arrays, or Maps requires a deep copy that recursively duplicates all nested values. This subtle distinction between shallow and deep copying is critical for maintaining data integrity in applications that work with complex state objects, whether you're building interactive forms, managing application state, or implementing undo/redo functionality in your web applications.

The Reference Problem

Consider a simple scenario where you need to modify an object without affecting the original. When you use assignment (const copy = original), both variables point to the same memory location. Any property changes through either variable are immediately visible through the other.

const original = { name: 'User', preferences: { theme: 'dark' } };
const copy = original;

copy.preferences.theme = 'light';
console.log(original.preferences.theme); // 'light' - original was modified!

This reference sharing becomes particularly problematic in modern web applications where state management is fundamental. Frameworks like React explicitly document the importance of immutability, which requires proper cloning techniques to achieve. Whether you're implementing form state management, building undo/redo functionality, or caching data that shouldn't mutate, understanding reference semantics is essential for writing bug-free code. When working with complex objects in Object Oriented JavaScript, proper cloning becomes even more critical as your object graphs grow more intricate.

Shallow Copy: Limited Duplication

A shallow copy creates a new object and copies the top-level properties from the source, but nested objects are still referenced rather than duplicated. JavaScript provides several methods for creating shallow copies, including the spread operator (...), Object.assign(), and array methods like slice() and concat().

const original = { name: 'User', tags: ['developer', 'designer'] };
const shallow = { ...original };

shallow.name = 'Admin';
shallow.tags.push('manager');

console.log(original.name); // 'User' - unchanged
console.log(original.tags); // ['developer', 'designer', 'manager'] - shared!

When shallow copy is appropriate: Shallow copying is the right choice when your data structure is flat--containing only primitive values like strings, numbers, booleans, null, and undefined at every level. Common use cases include creating modified versions of configuration objects, duplicating simple data transfer objects, or creating variations of static data that won't be mutated deeply. For flat objects containing only primitive values, shallow copying is sufficient and more performant than deep copying alternatives, making it ideal for quick prototyping where performance is critical and data complexity is low. Understanding when to use shallow versus deep copying is a key skill in Extend patterns where you need to create derived versions of existing objects.

Deep Copy: Complete Duplication

Deep copying creates a completely independent copy of an object, including all nested objects, arrays, Maps, Sets, and other reference types. The deep copy algorithm recursively traverses the entire object structure, creating new instances of every nested value. This ensures that no shared references exist between the original and the copy, which is essential for maintaining predictable application behavior.

The JSON Approach

For many years, the most common deep copy technique involved serializing objects to JSON strings and then parsing them back:

const original = { name: 'User', scores: [95, 87, 92] };
const deep = JSON.parse(JSON.stringify(original));

This technique became popular because it works with native JavaScript without any external dependencies. The V8 JavaScript engine specifically optimizes JSON.parse() for this pattern, making it relatively fast for simple objects.

However, the JSON approach has significant limitations that make it unsuitable for many real-world scenarios:

  • Functions are silently dropped: Methods and arrow functions disappear entirely
  • Symbols are not preserved: Symbol properties are lost during serialization
  • Date objects become strings: Dates become ISO date strings, not Date instances
  • Map, Set, and other built-ins throw errors: Cannot serialize collection types
  • Circular references cause stack overflow: Objects that reference themselves break the process
  • undefined values are omitted: Properties with undefined values are completely dropped
  • RegExp objects become empty objects: Pattern and flags are lost
  • Error objects lose their properties: Error messages and stack traces don't survive serialization

For simple data transfer objects containing only JSON-safe types, this approach remains viable and can actually be faster than structuredClone() due to V8 optimizations. But for modern web applications working with complex state objects, you'll need a more robust solution.

structuredClone(): The Modern Native Solution

The structuredClone() function is a native JavaScript API that creates deep copies using the structured clone algorithm. This algorithm was previously only available internally to the browser for operations like postMessage() and IndexedDB storage. Now, developers can access this powerful cloning mechanism directly.

const original = { name: 'User', metadata: new Map([['role', 'admin']]) };
const clone = structuredClone(original);

clone.metadata.set('role', 'superuser');
console.log(original.metadata.get('role')); // 'admin' - unchanged

Supported Types

structuredClone() handles a wide range of JavaScript types that JSON.stringify cannot handle:

  • All primitive types: strings, numbers, booleans, null, undefined, symbols
  • Arrays and nested objects: Complete recursive duplication
  • Date objects: Preserved as Date instances
  • Regular expressions: Preserved as RegExp instances
  • Set and Map collections: Properly cloned with all entries
  • ArrayBuffer and TypedArrays: Supported with transfer option
  • Error objects: Error, TypeError, and other error types
  • Custom objects and class instances: Properties are preserved (prototype chain lost)
  • Circular references: Properly handled without stack overflow

This comprehensive type support makes structuredClone() suitable for most deep copying needs in modern web applications.

The Transfer Option

One powerful feature of structuredClone() is its ability to transfer ArrayBuffers and other transferable objects rather than cloning them. This can significantly improve performance when working with large binary data, such as file uploads, image processing, or data synchronization between web workers:

const original = { buffer: new ArrayBuffer(16) };
const transferred = structuredClone(original, {
 transfer: [original.buffer]
});

console.log(original.buffer.byteLength); // 0 - transferred, now detached
console.log(transferred.buffer.byteLength); // 16 - clone has the memory

When you transfer a buffer, the original becomes detached and its byteLength becomes 0. This is particularly valuable when passing large data structures between web workers, avoiding the memory overhead of duplicating large binary objects while ensuring zero-copy semantics. The transfer option is essential for high-performance applications processing large datasets, making it ideal for scenarios involving HTML Text Basics where you might handle large text buffers.

Limitations and Edge Cases

Despite its power, structuredClone() has important limitations to understand when architecting your applications:

Functions are not supported: Attempting to clone an object containing functions, getters, or Symbol properties throws a DataCloneError. This is a deliberate security constraint--functions could contain malicious code. For objects with methods, you'll need alternative approaches.

Prototype chain loss: When cloning class instances, the result is a plain object with Object.prototype as its prototype. The original class methods and prototype properties are lost:

class User {
 constructor(name) {
 this.name = name;
 }
 greet() { return `Hello, ${this.name}`; }
}

const cloned = structuredClone(new User('Alice'));
console.log(cloned.greet); // undefined - method lost
console.log(cloned instanceof User); // false - prototype lost

DOM nodes cannot be cloned: HTML elements and other DOM nodes are not supported and will throw DataCloneError.

Property descriptors are lost: The cloned object has simple property assignments, losing the original property descriptors (writability, enumerability, configurability).

Error properties may vary: While Error objects can be cloned, specific properties like stack traces may not be preserved consistently across browsers.

Workarounds for these limitations: For scenarios requiring function preservation, use libraries like Lodash's cloneDeep() which can handle functions through custom cloning logic. For prototype retention, manually reconstruct the object: const clone = Object.assign(new User(), structuredClone(original)). Understanding these trade-offs is essential for building robust applications with proper state management patterns.

Performance Considerations

Performance characteristics vary significantly between cloning approaches. For simple JSON-safe objects, JSON.parse(JSON.stringify()) can actually be faster than structuredClone() due to V8-specific optimizations. However, for objects containing Dates, Maps, Sets, or other non-serializable types, structuredClone() is the only viable native option.

ApproachBest ForLimitations
JSON.parse/stringifyJSON-safe objects onlyDrops functions, Date, Map, Set, undefined
structuredClone()Complex objects, modern typesSlightly slower for simple cases
Spread operatorShallow copies onlyOnly top-level duplication
Lodash cloneDeep()Custom cloning needsExternal dependency, slower than native

Benchmark insights: The JSON approach is fastest for simple objects with only primitives, due to V8's specialized JSON parsing optimizations. However, structuredClone() has consistent performance regardless of object complexity. Both native methods are significantly faster than library implementations, and the transfer option provides dramatic speedup for large ArrayBuffers.

Recommendations for different scenarios:

  • Hot paths in performance-critical code: Profile with realistic data; JSON approach may win for simple structures
  • Complex objects with modern types: structuredClone() is the only viable native choice
  • Large binary data: Always use the transfer option to avoid memory duplication
  • General use: Default to structuredClone() for cleaner code and broader type support

When performance is critical, measure with your actual data structures rather than relying on general benchmarks. The "fastest" approach depends heavily on your specific object shapes and whether you need to preserve non-serializable types.

Practical Examples and Use Cases

State Management (Undo/Redo)

When implementing undo/redo functionality, deep copying is essential for preserving historical states without creating shared references that could lead to unexpected mutations:

class HistoryManager {
 constructor() {
 this.history = [];
 this.currentState = null;
 }

 push(state) {
 // Deep copy to preserve current state independently
 this.history.push(structuredClone(state));
 this.currentState = state;
 }

 undo() {
 if (this.history.length === 0) return null;
 const previous = this.history.pop();
 this.currentState = structuredClone(previous);
 return this.currentState;
 }
}

// Usage
const manager = new HistoryManager();
manager.push({ document: { title: 'Draft 1', content: '...' } });
manager.push({ document: { title: 'Draft 2', content: '...' } });
manager.undo(); // Returns to Draft 1 state

Data Transformation Pipelines

When processing data through multiple transformation stages, creating independent copies prevents mutations from propagating through the pipeline unexpectedly:

function processUserData(user, transformations) {
 // Create independent copy before transformations
 const workingCopy = structuredClone(user);

 for (const transform of transformations) {
 Object.assign(workingCopy, transform);
 }

 return workingCopy;
}

// Transformations won't affect the original user object
const result = processUserData(originalUser, [
 { preferences: { theme: 'dark' } },
 { metadata: { lastModified: new Date() } }
]);

Caching with Cache Invalidation

When caching expensive computations, returning deep copies ensures the cache entry isn't accidentally modified by consumers, maintaining cache integrity:

class DataCache {
 constructor() {
 this.cache = new Map();
 }

 getOrCompute(key, computeFn) {
 if (this.cache.has(key)) {
 // Return a deep copy to prevent external modification of cache
 return structuredClone(this.cache.get(key));
 }

 const value = computeFn();
 this.cache.set(key, value);
 return structuredClone(value);
 }
}

// Cache consumers cannot corrupt cached entries
const cache = new DataCache();
const data = cache.getOrCompute('user:123', () => fetchUserData());
data.modified = true; // Only modifies the returned copy
console.log(cache.cache.get('user:123').modified); // undefined - cache intact

Handling Complex Configuration Objects

Configuration objects often contain nested settings that must remain isolated when applying overrides, particularly in multi-environment deployments:

function createServiceConfig(baseConfig, overrides) {
 const config = structuredClone(baseConfig);

 for (const [key, value] of Object.entries(overrides)) {
 if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
 // Deep merge nested objects
 config[key] = { ...config[key], ...value };
 } else {
 config[key] = value;
 }
 }

 return config;
}

const baseConfig = {
 database: { host: 'localhost', port: 5432, pool: 10 },
 features: { darkMode: true, notifications: true }
};

const productionConfig = createServiceConfig(baseConfig, {
 database: { host: 'prod.db.example.com' }
});
// productionConfig.database.port remains 5432, only host changed

These patterns demonstrate how proper cloning enables robust state management, cache integrity, and predictable data flow in modern web applications. For related data processing patterns, see our guide on TransformStream for handling streaming data operations.

Best Practices for Cloning in Modern JavaScript

1. Default to structuredClone(): For most deep cloning needs, structuredClone() is the best choice. It handles complex types including Date, Map, Set, and RegExp, supports circular references, and is natively optimized by the browser. It should be your first consideration for any deep copy requirement.

2. Use spread operator for shallow copies: When you only need top-level duplication--such as updating a single property while preserving nested structure--the spread operator provides clean syntax with minimal performance overhead. For flat objects containing only primitive values, this is both sufficient and efficient.

3. Reserve JSON methods for specific scenarios: JSON.parse(JSON.stringify()) should only be used when you're certain the object contains only JSON-safe types and you need maximum serialization compatibility. This is appropriate for simple configuration objects, data transfer, or logging scenarios where type fidelity isn't critical.

4. Consider performance for hot paths: In frequently executed code paths, profile your cloning approach with realistic data. For objects with simple structures, JSON parsing may be faster; for complex objects with modern types, structuredClone() is both faster and more correct.

5. Handle errors gracefully: structuredClone() throws DataCloneError for unsupported types like functions or DOM nodes. Wrap cloning operations in try/catch blocks when the object structure is uncertain, and consider providing fallback mechanisms.

6. Document cloning requirements: When functions accept objects that will be cloned--or mutated--document the behavior clearly. This prevents confusion about whether callers can expect mutations to affect the original object. Clear documentation prevents subtle bugs in team collaboration.

7. Use transfer option for large binary data: When working with ArrayBuffers, TypedArrays, or large datasets, always use the transfer option to avoid memory duplication. This is particularly important when passing data between web workers or processing file uploads.

By following these practices, you'll write more predictable JavaScript that handles state correctly across complex applications. Proper cloning is fundamental to building reliable web applications that scale gracefully.

Frequently Asked Questions

What is the difference between shallow and deep copy?

A shallow copy only duplicates the top-level properties of an object. Nested objects remain as shared references between the original and the copy. A deep copy recursively duplicates all nested objects, creating a completely independent structure with no shared references. This distinction is fundamental for maintaining data integrity in applications with complex state objects.

Can I clone an object with functions using structuredClone()?

No, structuredClone() will throw a DataCloneError if the object contains functions, Symbols, getters, or other non-cloneable types. For objects with methods, use Lodash's cloneDeep() which can handle functions through custom cloning logic, or manually reconstruct the object with Object.assign().

Does structuredClone() preserve Date objects?

Yes, structuredClone() properly preserves Date objects as Date instances, unlike JSON.stringify() which converts them to ISO date strings. This makes structuredClone() essential for applications working with temporal data.

How do I clone a class instance with methods?

structuredClone() will lose the prototype chain and return a plain object. For class instances, you may need to use Object.assign() combined with manually copying properties, or use a library like Lodash cloneDeep(). The workaround is: `const clone = Object.assign(new OriginalClass(), structuredClone(original))`.

What is the transfer option in structuredClone()?

The transfer option allows you to specify ArrayBuffers that should be transferred rather than cloned. The original buffer becomes detached (byteLength becomes 0), while the clone receives the buffer's memory. This provides zero-copy semantics and is essential for performance when working with large binary data between web workers.

Is structuredClone() faster than JSON.parse(JSON.stringify())?

For complex objects with Date, Map, Set, or other non-serializable types, structuredClone() is the only viable native option. For simple JSON-safe objects, JSON parsing may be slightly faster due to V8 optimizations. Profile with your actual data to determine the best approach for performance-critical code.

Conclusion

JavaScript's cloning capabilities have matured significantly with the introduction of structuredClone(). This native API provides a robust solution for deep copying complex objects, handling circular references, and supporting modern data types like Map, Set, and Date. While JSON-based approaches remain useful for specific scenarios with simple data structures, structuredClone() should be the default choice for modern web development.

Understanding the distinction between shallow and deep copying is fundamental to writing reliable JavaScript applications. By choosing the appropriate cloning strategy based on your data structure and requirements, you can prevent subtle bugs, maintain data integrity, and build more predictable applications. Whether you're implementing undo/redo functionality, managing application state, or caching expensive computations, proper cloning patterns will help you write cleaner, more maintainable code.

The patterns and practices outlined in this guide apply across all modern JavaScript environments, from React applications to vanilla JavaScript projects. By mastering these cloning techniques, you'll be better equipped to handle complex state management challenges and build more robust web applications.

Sources

  1. MDN Web Docs: Window structuredClone() - Official browser API documentation with comprehensive coverage of syntax, parameters, examples, browser support, and limitations
  2. web.dev: Deep-copying in JavaScript using structuredClone - Google's developer resource explaining structured clone algorithm, performance comparisons, and practical use cases
  3. MDN: Deep copy - Glossary definition covering deep vs shallow copy concepts and serialization approaches