What is the difference between a deep copy and a shallow copy?

Learn what is the difference between a deep copy and a shallow copy? with practical examples, diagrams, and best practices. Covers language-agnostic, copy, deep-copy development techniques with vis...

Deep Copy vs. Shallow Copy: Understanding Data Duplication

Hero image for What is the difference between a deep copy and a shallow copy?

Explore the fundamental differences between deep and shallow copies, their implications for data integrity, and when to use each in programming.

In programming, copying data structures is a common operation. However, the way you copy can significantly impact how your program behaves, especially when dealing with complex objects that contain references to other objects. This article delves into the concepts of shallow copy and deep copy, explaining their mechanisms, use cases, and potential pitfalls.

What is a Shallow Copy?

A shallow copy creates a new object, but it does not create copies of the nested objects or referenced data within the original object. Instead, it copies the references to those nested objects. This means that both the original and the shallow-copied object will point to the same underlying nested data. If you modify a nested object through either the original or the copied object, the changes will be reflected in both.

flowchart LR
    subgraph Original Object
        A[Original Parent] --> B(Nested Object 1)
        A --> C(Nested Object 2)
    end
    subgraph Shallow Copy
        D[Shallow Copy Parent] --> B
        D --> C
    end
    style Original Object fill:#f9f,stroke:#333,stroke-width:2px
    style Shallow Copy fill:#ccf,stroke:#333,stroke-width:2px
    B:::shared
    C:::shared
    classDef shared fill:#afa,stroke:#333,stroke-width:2px
    linkStyle 0,1,2,3 stroke-width:2px,fill:none,stroke:black;

Shallow Copy: Both parent objects reference the same nested objects.

import copy

original_list = [1, [2, 3], 4]
shallow_copied_list = list(original_list) # Or original_list[:]

print(f"Original: {original_list}")
print(f"Shallow Copy: {shallow_copied_list}")

# Modify a nested object in the shallow copy
shallow_copied_list[1][0] = 99

print(f"\nAfter modification:")
print(f"Original: {original_list}") # Original is also changed!
print(f"Shallow Copy: {shallow_copied_list}")

Python example demonstrating a shallow copy and its effect on nested mutable objects.

What is a Deep Copy?

A deep copy, in contrast, creates a completely independent duplicate of the original object. This means it not only copies the top-level object but also recursively copies all nested objects and their contents. As a result, the original and the deep-copied object are entirely separate entities. Modifications to one will not affect the other.

flowchart LR
    subgraph Original Object
        A[Original Parent] --> B(Nested Object 1)
        A --> C(Nested Object 2)
    end
    subgraph Deep Copy
        D[Deep Copy Parent] --> E(Nested Object 1 Copy)
        D --> F(Nested Object 2 Copy)
    end
    style Original Object fill:#f9f,stroke:#333,stroke-width:2px
    style Deep Copy fill:#ccf,stroke:#333,stroke-width:2px
    B:::original
    C:::original
    E:::copy
    F:::copy
    classDef original fill:#afa,stroke:#333,stroke-width:2px
    classDef copy fill:#add8e6,stroke:#333,stroke-width:2px
    linkStyle 0,1,2,3 stroke-width:2px,fill:none,stroke:black;

Deep Copy: Both parent objects and their nested objects are independent.

import copy

original_list = [1, [2, 3], 4]
deep_copied_list = copy.deepcopy(original_list)

print(f"Original: {original_list}")
print(f"Deep Copy: {deep_copied_list}")

# Modify a nested object in the deep copy
deep_copied_list[1][0] = 99

print(f"\nAfter modification:")
print(f"Original: {original_list}") # Original remains unchanged
print(f"Deep Copy: {deep_copied_list}")

Python example demonstrating a deep copy and its independence from the original.

When to Use Which?

The choice between a shallow and a deep copy depends entirely on your specific requirements and the nature of the data you are working with.

  • Shallow Copy: Use a shallow copy when your object contains only immutable data types (like numbers, strings, tuples in Python) or when you intentionally want changes to nested mutable objects to be reflected across all copies. It's also suitable for performance-critical scenarios where a full duplication is unnecessary.

  • Deep Copy: Opt for a deep copy when your object contains mutable nested objects and you need complete independence between the original and the copy. This is crucial for maintaining data integrity and preventing unexpected side effects, especially in scenarios like undo/redo functionalities, state management, or when passing objects to functions that might modify them.

JavaScript

const original = { a: 1, b: { c: 2 } };

// Shallow Copy (using spread operator or Object.assign) const shallowCopy = { ...original }; shallowCopy.a = 10; shallowCopy.b.c = 20; // Modifies original.b.c as well

console.log('Original (shallow):', original); // { a: 1, b: { c: 20 } } console.log('Shallow Copy:', shallowCopy); // { a: 10, b: { c: 20 } }

// Deep Copy (using JSON.parse(JSON.stringify()) - has limitations) const original2 = { a: 1, b: { c: 2 }, d: new Date() }; const deepCopy = JSON.parse(JSON.stringify(original2)); deepCopy.a = 10; deepCopy.b.c = 20;

console.log('\nOriginal (deep):', original2); // { a: 1, b: { c: 2 }, d: Date object } console.log('Deep Copy:', deepCopy); // { a: 10, b: { c: 20 }, d: Date string }

// For robust deep copy in JS, consider libraries like Lodash's _.cloneDeep

Java

class Address { String street; Address(String street) { this.street = street; } @Override public String toString() { return "Address{" + "street='" + street + ''' + '}'; } }

class Person implements Cloneable { String name; Address address;

Person(String name, Address address) {
    this.name = name;
    this.address = address;
}

// Shallow Copy (default Object.clone() behavior)
@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}

// Deep Copy (manual implementation)
public Person deepClone() throws CloneNotSupportedException {
    Person clonedPerson = (Person) super.clone();
    clonedPerson.address = new Address(this.address.street); // Deep copy the Address object
    return clonedPerson;
}

@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", address=" + address + '}'; }

}

public class CopyExample { public static void main(String[] args) throws CloneNotSupportedException { Address originalAddress = new Address("123 Main St"); Person originalPerson = new Person("Alice", originalAddress);

    // Shallow Copy
    Person shallowCopyPerson = (Person) originalPerson.clone();
    shallowCopyPerson.name = "Bob";
    shallowCopyPerson.address.street = "456 Oak Ave"; // Modifies originalAddress as well

    System.out.println("Original Person (shallow): " + originalPerson);
    System.out.println("Shallow Copy Person: " + shallowCopyPerson);

    // Deep Copy
    Address originalAddress2 = new Address("789 Pine Ln");
    Person originalPerson2 = new Person("Charlie", originalAddress2);
    Person deepCopyPerson = originalPerson2.deepClone();
    deepCopyPerson.name = "David";
    deepCopyPerson.address.street = "101 Elm Rd"; // Only modifies deepCopyPerson's address

    System.out.println("\nOriginal Person (deep): " + originalPerson2);
    System.out.println("Deep Copy Person: " + deepCopyPerson);
}

}

C#

using System;

public class Address { public string Street { get; set; } public Address(string street) { Street = street; } public override string ToString() => $"Address{{Street='{Street}'}}"; }

public class Person { public string Name { get; set; } public Address HomeAddress { get; set; }

public Person(string name, Address address)
{
    Name = name;
    HomeAddress = address;
}

// Shallow Copy (MemberwiseClone)
public Person ShallowCopy()
{
    return (Person)this.MemberwiseClone();
}

// Deep Copy (manual implementation)
public Person DeepCopy()
{
    Person deepClonedPerson = (Person)this.MemberwiseClone();
    deepClonedPerson.HomeAddress = new Address(this.HomeAddress.Street); // Deep copy the Address object
    return deepClonedPerson;
}

public override string ToString() => $"Person{{Name='{Name}', Address={HomeAddress}}}";

}

public class CopyExample { public static void Main(string[] args) { Address originalAddress = new Address("123 Main St"); Person originalPerson = new Person("Alice", originalAddress);

    // Shallow Copy
    Person shallowCopyPerson = originalPerson.ShallowCopy();
    shallowCopyPerson.Name = "Bob";
    shallowCopyPerson.HomeAddress.Street = "456 Oak Ave"; // Modifies originalAddress as well

    Console.WriteLine($"Original Person (shallow): {originalPerson}");
    Console.WriteLine($"Shallow Copy Person: {shallowCopyPerson}");

    // Deep Copy
    Address originalAddress2 = new Address("789 Pine Ln");
    Person originalPerson2 = new Person("Charlie", originalAddress2);
    Person deepCopyPerson = originalPerson2.DeepCopy();
    deepCopyPerson.Name = "David";
    deepCopyPerson.HomeAddress.Street = "101 Elm Rd"; // Only modifies deepCopyPerson's address

    Console.WriteLine($"\nOriginal Person (deep): {originalPerson2}");
    Console.WriteLine($"Deep Copy Person: {deepCopyPerson}");
}

}