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]] 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]] 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! Deep Copy:
// JavaScript deep copy (using JSON methods) const deep = JSON.parse(JSON.stringify(original)); console.log(deep.address === original.address); // false - different objects! 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! 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! 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") 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! 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! When to Use Each
Use Shallow Copy When:
- 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 - Performance is critical
// When dealing with large objects where nested mutations aren't needed const config = {...defaultConfig}; // Fast shallow copy - 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 Use Deep Copy When:
- 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) - 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(); } } 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 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; } 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)