DEV Community

Cover image for Shallow Copy vs Deep Copy: Key Differences
Aditya Pratap Bhuyan
Aditya Pratap Bhuyan

Posted on

Shallow Copy vs Deep Copy: Key Differences

What is a Shallow Copy?

A shallow copy creates a new object, but inserts references to the objects found in the original. The copied object shares the same nested objects with the original object.

# Python example of shallow copy import copy original = [[1, 2, 3], [4, 5, 6]] shallow = copy.copy(original) # or original.copy() for lists  # Modifying nested object affects both shallow[0][0] = 'X' print(original) # [['X', 2, 3], [4, 5, 6]] - original is affected! print(shallow) # [['X', 2, 3], [4, 5, 6]] 
Enter fullscreen mode Exit fullscreen mode

What is a Deep Copy?

A deep copy creates a new object and recursively copies all nested objects. The copied object is completely independent of the original.

# Python example of deep copy import copy original = [[1, 2, 3], [4, 5, 6]] deep = copy.deepcopy(original) # Modifying nested object only affects the copy deep[0][0] = 'X' print(original) # [[1, 2, 3], [4, 5, 6]] - original unchanged! print(deep) # [['X', 2, 3], [4, 5, 6]] 
Enter fullscreen mode Exit fullscreen mode

Key Differences

1. Memory Allocation

Shallow Copy:

// JavaScript example const original = { name: 'John', address: { city: 'New York', zip: '10001' } }; const shallow = Object.assign({}, original); // or using spread operator: const shallow = {...original}; console.log(shallow.address === original.address); // true - same reference! 
Enter fullscreen mode Exit fullscreen mode

Deep Copy:

// JavaScript deep copy (using JSON methods) const deep = JSON.parse(JSON.stringify(original)); console.log(deep.address === original.address); // false - different objects! 
Enter fullscreen mode Exit fullscreen mode

2. Independence Level

Shallow Copy - Partial Independence:

// Java example with ArrayList ArrayList<StringBuilder> original = new ArrayList<>(); original.add(new StringBuilder("Hello")); original.add(new StringBuilder("World")); ArrayList<StringBuilder> shallow = (ArrayList<StringBuilder>) original.clone(); // Modifying nested object shallow.get(0).append(" Java"); System.out.println(original.get(0)); // "Hello Java" - affected! 
Enter fullscreen mode Exit fullscreen mode

Deep Copy - Complete Independence:

// Java deep copy implementation ArrayList<StringBuilder> deep = new ArrayList<>(); for (StringBuilder sb : original) { deep.add(new StringBuilder(sb.toString())); } deep.get(0).append(" Java"); System.out.println(original.get(0)); // "Hello" - unaffected! 
Enter fullscreen mode Exit fullscreen mode

3. Performance Impact

import time import copy # Performance comparison data = [[i for i in range(1000)] for j in range(1000)] # Shallow copy - faster start = time.time() shallow = copy.copy(data) print(f"Shallow copy time: {time.time() - start:.4f} seconds") # Deep copy - slower start = time.time() deep = copy.deepcopy(data) print(f"Deep copy time: {time.time() - start:.4f} seconds") 
Enter fullscreen mode Exit fullscreen mode

4. Handling Different Data Types

Primitive vs Reference Types:

// C# example public class Person { public string Name { get; set; } public Address Address { get; set; } } public class Address { public string City { get; set; } } // Shallow copy Person original = new Person { Name = "John", Address = new Address { City = "NYC" } }; Person shallow = new Person { Name = original.Name, // String is immutable, so this is safe Address = original.Address // Reference copy - dangerous! }; shallow.Address.City = "LA"; Console.WriteLine(original.Address.City); // "LA" - changed! 
Enter fullscreen mode Exit fullscreen mode

Visual Representation

Shallow Copy: Original: [ref1] → {a: 1, b: [ref2] → [1, 2, 3]} Copy: [ref3] → {a: 1, b: [ref2] → [1, 2, 3]} ↑ Same reference! Deep Copy: Original: [ref1] → {a: 1, b: [ref2] → [1, 2, 3]} Copy: [ref3] → {a: 1, b: [ref4] → [1, 2, 3]} ↑ New reference! 
Enter fullscreen mode Exit fullscreen mode

When to Use Each

Use Shallow Copy When:

  1. Working with immutable nested objects
# Safe with immutable nested objects original = {"name": "John", "age": 30, "scores": (90, 85, 88)} shallow = original.copy() # Tuple is immutable, so it's safe 
Enter fullscreen mode Exit fullscreen mode
  1. Performance is critical
// When dealing with large objects where nested mutations aren't needed const config = {...defaultConfig}; // Fast shallow copy 
Enter fullscreen mode Exit fullscreen mode
  1. You want shared state
# Intentionally sharing nested objects shared_cache = {"data": []} client1 = {"id": 1, "cache": shared_cache} client2 = {"id": 2, "cache": shared_cache} # Both share same cache 
Enter fullscreen mode Exit fullscreen mode

Use Deep Copy When:

  1. Complete independence is required
# Creating independent copies for parallel processing import multiprocessing def process_data(data_copy): # Modify data_copy without affecting original  data_copy['results'] = compute_results(data_copy) return data_copy original_data = {"values": [[1, 2], [3, 4]], "results": None} processes = [] for i in range(4): data_copy = copy.deepcopy(original_data) # Each process gets independent copy  p = multiprocessing.Process(target=process_data, args=(data_copy,)) processes.append(p) 
Enter fullscreen mode Exit fullscreen mode
  1. Creating backups or snapshots
// State management - creating immutable snapshots class StateManager { constructor() { this.history = []; } saveState(state) { // Deep copy to preserve exact state at this moment this.history.push(JSON.parse(JSON.stringify(state))); } undo() { return this.history.pop(); } } 
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls and Solutions

1. Circular References

# Problem: Circular references obj1 = {"name": "A"} obj2 = {"name": "B"} obj1["ref"] = obj2 obj2["ref"] = obj1 # Standard deep copy handles this deep = copy.deepcopy(obj1) # Works correctly!  # JSON approach fails # json_copy = JSON.parse(JSON.stringify(obj1)) # Error: Circular reference 
Enter fullscreen mode Exit fullscreen mode

2. Special Objects

// Problem: Functions and special objects aren't copied by JSON const original = { date: new Date(), regex: /test/gi, func: () => console.log("Hello"), undefined: undefined }; const jsonCopy = JSON.parse(JSON.stringify(original)); console.log(jsonCopy); // {date: "2024-01-20T..."} - lost types! // Solution: Custom deep copy function function deepCopy(obj) { if (obj === null || typeof obj !== "object") return obj; if (obj instanceof Date) return new Date(obj.getTime()); if (obj instanceof Array) return obj.map(item => deepCopy(item)); if (obj instanceof RegExp) return new RegExp(obj); const clonedObj = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepCopy(obj[key]); } } return clonedObj; } 
Enter fullscreen mode Exit fullscreen mode

Summary

Aspect Shallow Copy Deep Copy
Nested Objects Shared references Independent copies
Memory Usage Lower Higher
Performance Faster Slower
Modification Safety Risk of unintended changes Safe from side effects
Use Case Flat objects or intentional sharing Complete independence needed
Implementation Complexity Simple Complex for custom objects

The choice between shallow and deep copy depends on your specific needs regarding data independence, performance requirements, and the structure of your objects. Understanding these differences is crucial for avoiding bugs related to unintended object mutations and managing memory efficiently.

Top comments (0)