Featured image of post JavaScript Shallow Copy vs Deep Copy: 2025 Edition

JavaScript Shallow Copy vs Deep Copy: 2025 Edition

Struggling with JavaScript copy operations? Learn critical differences between shallow and deep copying, when to use each, and master latest techniques including structuredClone().

💡 Having some background knowledge about JavaScript Primitive vs Reference will make this article much easier to follow and understand.

When working with JavaScript objects and arrays, you’ve probably encountered this frustrating scenario: “I copied my data to a new variable, but why is my original data changing too?” Sound familiar?

I remember struggling with this exact problem early in my development journey. I thought I was creating independent copies of my objects, but my original data kept getting modified unexpectedly. This became especially problematic when building interactive applications where user actions would accidentally overwrite important data. Then I had that “aha!” moment when I finally understood the difference between shallow and deep copying – it completely changed how I approach data manipulation in JavaScript.

In this comprehensive guide, I’ll walk you through everything you need to know about JavaScript copying methods. We’ll cover the fundamental concepts, explore 5 different copying techniques, and see practical examples that you’ll encounter in real-world development.

From basic spread operators to the latest structuredClone() method, you’ll learn exactly when and how to use each approach to avoid those sneaky bugs that can crash your applications.


What is JavaScript Copy?

JavaScript Copy: The Essential Definition

Shallow copy creates a new object but keeps references to nested objects from the original. Deep copy creates a completely independent duplicate with no shared references. The choice between them depends on your data structure and whether you need complete isolation from the original object.



Why JavaScript Copy Methods Matter in Real Development

The Root Cause: Reference vs Value Types

Understanding copying issues requires knowledge of how JavaScript stores different data types.

JavaScript has two fundamental data categories:

  • Primitive Types: string, number, boolean, null, undefined, symbol

    • Stored as actual values
    • Copying always creates independent duplicates
  • Reference Types: object, array, function

    • Stored as memory addresses (references)
    • Copying requires special attention
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Primitive types - no problems here
let originalPrice = 99.99;
let discountedPrice = originalPrice;
discountedPrice = 79.99;
console.log(originalPrice); // 99.99 (unchanged)

// Reference types - this is where things get tricky!
let originalCart = ['laptop', 'mouse', 'keyboard'];
let userCart = originalCart;  // Points to the same memory location
userCart.push('monitor');
console.log(originalCart); // ['laptop', 'mouse', 'keyboard', 'monitor'] (original changed!)

Why This Matters in Real Applications

In production environments, most data is structured as objects or arrays: user profiles, product inventories, form data, API responses. Without proper copying techniques, you risk:

  • Accidental data corruption
  • Unexpected UI updates
  • Hard-to-debug state management issues
  • Performance problems from unwanted re-renders

Understanding Shallow Copy

What is Shallow Copy?

Shallow copy creates a new object for the top level only, but nested objects and arrays still share references with the original.

Think of it like copying a filing cabinet. You get a new cabinet (top level), but the folders inside (nested objects) are the same ones from the original cabinet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const productCatalog = {
    title: "Electronics Store",     // Top level (gets copied)
    categories: {                   // Nested object (shared reference)
        laptops: ['MacBook', 'ThinkPad'],
        phones: ['iPhone', 'Samsung']
    }
};

// Common shallow copy methods
const catalog1 = Object.assign({}, productCatalog);
const catalog2 = { ...productCatalog };  // Spread operator (most popular)

// Top-level changes are independent
catalog1.title = "Tech Warehouse";
console.log(productCatalog.title); // "Electronics Store" (original intact)
console.log(catalog1.title);       // "Tech Warehouse"

// Nested changes affect both objects!
catalog1.categories.laptops.push('Dell XPS');
console.log(productCatalog.categories.laptops); // ['MacBook', 'ThinkPad', 'Dell XPS'] (original modified!)
console.log(catalog1.categories.laptops);       // ['MacBook', 'ThinkPad', 'Dell XPS']

When to Use Shallow Copy

Shallow copying is perfect when you need:

  1. Simple object structures without nesting
  2. Performance optimization (faster than deep copy)
  3. Memory efficiency (shared references save space)
1
2
3
4
5
6
7
8
9
// ✅ Perfect for shallow copy
const userProfile = {
    username: "johndoe",
    email: "john@example.com",
    isActive: true
};

const updatedProfile = { ...userProfile, isActive: false };
// Simple structure means no reference issues!

Mastering Deep Copy

What is Deep Copy and When You Need It

Deep copy creates a completely independent duplicate at every level. All nested objects, arrays, and properties are newly created with no shared references.

This is like photocopying every single document in that filing cabinet and putting them in a brand new cabinet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const applicationState = {
    currentUser: "alice_dev",
    preferences: {
        theme: "dark",
        notifications: {
            email: true,
            push: false,
            desktop: true
        }
    },
    recentProjects: ["weather-app", "todo-list"]
};

// Deep copy using structuredClone (2025 recommended method!)
const backupState = structuredClone(applicationState);

// Change deeply nested values
backupState.preferences.notifications.email = false;
backupState.recentProjects.push("portfolio-site");

console.log(applicationState.preferences.notifications.email);  // true (original preserved!)
console.log(backupState.preferences.notifications.email);       // false
console.log(applicationState.recentProjects);                   // ["weather-app", "todo-list"]
console.log(backupState.recentProjects);                        // ["weather-app", "todo-list", "portfolio-site"]

Essential Use Cases for Deep Copy

You absolutely need deep copy when:

  1. Working with nested data structures
  2. Preserving original data integrity
  3. Creating backup copies before modifications
  4. Managing application state safely

5 JavaScript Copy Methods Compared

Method 1: structuredClone() - The Modern Standard

The newest and most reliable method, built into modern browsers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const complexData = {
    id: 'user_123',
    createdAt: new Date('2024-01-15'),
    tags: new Set(['developer', 'javascript', 'react']),
    metadata: new Map([['version', '1.0'], ['env', 'production']]),
    config: {
        settings: {
            autoSave: true,
            theme: 'dark'
        }
    }
};

const perfectCopy = structuredClone(complexData);

// All complex types are properly preserved!
console.log(perfectCopy.createdAt instanceof Date);     // true
console.log(perfectCopy.tags.has('developer'));         // true
console.log(perfectCopy.metadata.get('version'));       // '1.0'

Benefits of structuredClone():

  • No external libraries required
  • Handles Date, Map, Set, RegExp, and more
  • Manages circular references automatically
  • Excellent performance

Limitations:

  • Cannot copy functions
  • Not supported in very old browsers (but widely available in 2025)

Method 2: JSON Parse/Stringify - Simple but Limited

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ❌ Problematic example
const dataWithComplexTypes = {
    name: "DevProject",
    launchDate: new Date(),                        // Becomes string
    execute: function() { return "Running"; },     // Disappears
    status: undefined,                             // Disappears
    config: null                                   // Preserved as null
};

const jsonCopy = JSON.parse(JSON.stringify(dataWithComplexTypes));
console.log(jsonCopy.launchDate instanceof Date); // false - now a string!
console.log(jsonCopy.execute);                     // undefined - function lost!

// ✅ Safe usage example
const basicProjectData = {
    projectName: "My Website",
    version: "2.1.0",
    contributors: ["Alice", "Bob", "Charlie"],
    isPublic: true
};

const safeCopy = JSON.parse(JSON.stringify(basicProjectData));
// Works perfectly with basic data types

Why JSON Method Has Limitations:

JSON was designed for data interchange between servers and clients, so it only supports basic, serializable data types. JavaScript-specific objects like functions, Date objects, or undefined values can’t be represented in JSON format.


Method 3: Spread Operator (…) - Best for Shallow Copy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Perfect for simple objects
const taskItem = {
    id: 1,
    text: "Learn JavaScript copying",
    completed: false,
    priority: "high"
};

const updatedTask = { ...taskItem, completed: true };

// Great for arrays too
const originalTasks = [
    { id: 1, text: "Task 1" },
    { id: 2, text: "Task 2" }
];

const newTaskList = [...originalTasks, { id: 3, text: "Task 3" }];

Method 4: Object.assign() - Alternative Shallow Copy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const defaultConfig = {
    apiUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3
};

const userConfig = {
    timeout: 10000,
    debug: true
};

// Merge configurations
const finalConfig = Object.assign({}, defaultConfig, userConfig);
// Results in: { apiUrl: "https://api.example.com", timeout: 10000, retries: 3, debug: true }

Method 5: Lodash cloneDeep() - When You Need Functions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// npm install lodash
import _ from 'lodash';

const moduleWithMethods = {
    name: "UserManager",
    data: { users: [] },
    addUser: function(user) { this.data.users.push(user); },
    getCount: () => this.data.users.length
};

const clonedModule = _.cloneDeep(moduleWithMethods);
console.log(clonedModule.addUser.toString()); // Function is preserved!


2025 Best Practices Guide

Choosing the Right Method: Decision Matrix

ScenarioRecommended MethodWhy This Choice
Simple objects + performance criticalSpread operator {...obj}Fastest and sufficient
Nested objects + no special typesstructuredClone()Safe and reliable
Complex types (Date, Map, Set)structuredClone()Preserves type integrity
Need to copy functions_.cloneDeep()Only method that handles functions
Legacy browser support neededJSON method + validationMaximum compatibility

Real-World Implementation Examples

Here’s how these copying methods solve actual development challenges:


1. Form Data Management

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Managing form state without mutations
const originalFormData = {
    personalInfo: {
        firstName: "John",
        lastName: "Doe",
        email: "john@example.com"
    },
    preferences: {
        newsletter: true,
        notifications: false
    }
};

// User makes changes - create independent copy
const formDraft = structuredClone(originalFormData);
formDraft.personalInfo.firstName = "Jane";
formDraft.preferences.newsletter = false;

// Original data remains untouched for reset functionality
console.log(originalFormData.personalInfo.firstName); // "John" (preserved!)

2. Shopping Cart State Management

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const shoppingCart = {
    items: [
        { productId: 'P1', name: 'Laptop', quantity: 1, price: 999 },
        { productId: 'P2', name: 'Mouse', quantity: 2, price: 25 }
    ],
    discounts: {
        code: 'SAVE10',
        percentage: 10
    }
};

// Add new item without affecting original cart
const updatedCart = structuredClone(shoppingCart);
updatedCart.items.push({ productId: 'P3', name: 'Keyboard', quantity: 1, price: 75 });

// Safe to modify without side effects
console.log(shoppingCart.items.length);     // 2 (original preserved)
console.log(updatedCart.items.length);      // 3 (new version)

3. API Response Processing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const apiResponse = {
    timestamp: "2024-01-15T10:30:00Z",  // String format from API
    userData: {
        profile: {
            name: "Developer",
            joinDate: "2023-06-01T00:00:00Z"
        },
        settings: { theme: "auto" }
    }
};

// Process data while preserving original
const processedData = structuredClone(apiResponse);
processedData.timestamp = new Date(processedData.timestamp);
processedData.userData.profile.joinDate = new Date(processedData.userData.profile.joinDate);
processedData.userData.settings.theme = "dark";

// Original API response stays intact for debugging
console.log(typeof apiResponse.timestamp);              // "string"
console.log(processedData.timestamp instanceof Date);   // true

Frequently Asked Questions

How do I know when to use shallow vs deep copy?

Examine your object structure first. If you have nested objects or arrays that you plan to modify, use deep copy. For simple, flat objects, shallow copy is sufficient and faster. When in doubt, structuredClone() is the safest choice. In my experience, I follow the principle: “Start with structuredClone() and optimize only if performance becomes an issue.”


Is structuredClone() always better than JSON methods?

In most cases, yes. structuredClone() preserves data types accurately while JSON methods can transform or lose data (functions, Date objects, undefined values). The performance difference is negligible for typical use cases. However, for processing thousands of simple objects in performance-critical applications, JSON methods might have a slight speed advantage.


Can I copy functions with structuredClone()?

No, structuredClone() cannot copy functions. If you need to copy functions, use Lodash’s cloneDeep(). However, in modern JavaScript development, it’s often better to separate data from behavior—keep your data as plain objects and functions separate for better architecture and testability.


Which method performs best?

For shallow copying:

  • Spread operator ({...obj}, [...arr]) is fastest
  • Perfect balance of speed and correctness for simple structures

For deep copying:

  • Performance ranking: JSON methods > structuredClone() > Lodash
  • Reliability ranking: structuredClone() > Lodash > JSON methods

But remember: correctness trumps performance. structuredClone() offers:

  • Perfect type preservation (Date, Map, Set, RegExp)
  • Circular reference handling (JSON methods throw errors)
  • Predictable results (no data transformation surprises)
  • Built-in availability (no external dependencies)

The performance difference is usually negligible (milliseconds), so prioritize safe, bug-free code with structuredClone()!


What about copying arrays?

Arrays follow the same principles as objects. Use spread operator [...array] for shallow copying simple arrays, and structuredClone(array) for arrays containing objects or nested arrays. Array methods like slice() also create shallow copies but spread operator is more readable and consistent.


Key Takeaways

Let’s summarize the essential points about JavaScript copying:

  • Shallow Copy: Copies only the top level, perfect for simple structures
  • Deep Copy: Creates completely independent copies, essential for nested data
  • 2025 Recommendations: Spread operator for shallow, structuredClone() for deep copying
  • Golden Rule: When uncertain, choose structuredClone() for safety


What’s Next?

Practice these concepts by building a simple todo list application. Create, modify, and delete tasks while ensuring your original data stays protected. Try implementing undo functionality using these copying techniques—you’ll quickly see why proper copying is crucial for robust applications.

Now that you understand how JavaScript manages data through copying, you might be wondering: “How does JavaScript actually find and access these variables in the first place?” The answer lies in understanding JavaScript’s lexical environment—the sophisticated system that tracks where variables live and how they can be accessed.

In our next article, we’ll dive deep into the fascinating world of variable lookup, scope chains, and closures. You’ll discover why your variables sometimes behave unexpectedly and learn the fundamental concepts that make JavaScript’s variable management system tick. Understanding lexical environments will make you a much more confident developer when working with complex data structures and closures.

What’s been your experience with JavaScript copying? Have you encountered any tricky situations or discovered clever solutions? Share your stories in the comments below—let’s learn together! 🚀


Hugo로 만듦
JimmyStack 테마 사용 중