Shallow Copy vs Deep Copy: Key Differences

Shallow Copy vs Deep Copy: Key Differences

Shallow Copy vs Deep Copy

What is a Shallow Copy?

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?

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:

  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
  1. Performance is critical
// When dealing with large objects where nested mutations aren't needed
const config = {...defaultConfig};  // Fast shallow copy
  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

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)
  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();
    }
}

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

AspectShallow CopyDeep Copy
Nested ObjectsShared referencesIndependent copies
Memory UsageLowerHigher
PerformanceFasterSlower
Modification SafetyRisk of unintended changesSafe from side effects
Use CaseFlat objects or intentional sharingComplete independence needed
Implementation ComplexitySimpleComplex 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.

Aditya: Cloud Native Specialist, Consultant, and Architect Aditya is a seasoned professional in the realm of cloud computing, specializing as a cloud native specialist, consultant, architect, SRE specialist, cloud engineer, and developer. With over two decades of experience in the IT sector, Aditya has established themselves as a proficient Java developer, J2EE architect, scrum master, and instructor. His career spans various roles across software development, architecture, and cloud technology, contributing significantly to the evolution of modern IT landscapes. Based in Bangalore, India, Aditya has cultivated a deep expertise in guiding clients through transformative journeys from legacy systems to contemporary microservices architectures. He has successfully led initiatives on prominent cloud computing platforms such as AWS, Google Cloud Platform (GCP), Microsoft Azure, and VMware Tanzu. Additionally, Aditya possesses a strong command over orchestration systems like Docker Swarm and Kubernetes, pivotal in orchestrating scalable and efficient cloud-native solutions. Aditya's professional journey is underscored by a passion for cloud technologies and a commitment to delivering high-impact solutions. He has authored numerous articles and insights on Cloud Native and Cloud computing, contributing thought leadership to the industry. His writings reflect a deep understanding of cloud architecture, best practices, and emerging trends shaping the future of IT infrastructure. Beyond his technical acumen, Aditya places a strong emphasis on personal well-being, regularly engaging in yoga and meditation to maintain physical and mental fitness. This holistic approach not only supports his professional endeavors but also enriches his leadership and mentorship roles within the IT community. Aditya's career is defined by a relentless pursuit of excellence in cloud-native transformation, backed by extensive hands-on experience and a continuous quest for knowledge. His insights into cloud architecture, coupled with a pragmatic approach to solving complex challenges, make them a trusted advisor and a sought-after consultant in the field of cloud computing and software architecture.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top