Python - Python Shallow Copy vs Deep Copy

When working with Python objects, it is important to understand how copying works. Assigning one variable to another does not necessarily create a new copy of an object. Instead, both variables may refer to the same object in memory. Python provides different ways to copy objects, and two important concepts are shallow copying and deep copying.

Understanding the difference between shallow copy and deep copy is especially important when working with nested lists, dictionaries, custom objects, and other mutable data structures. Choosing the wrong type of copy can cause unexpected changes to the original data.

1. What Is Copying in Python?

Consider the following example:

numbers = [10, 20, 30]
new_numbers = numbers

Here, new_numbers is not an independent copy of numbers. Both variables refer to the same list.

new_numbers.append(40)

print(numbers)
print(new_numbers)

Output:

[10, 20, 30, 40]
[10, 20, 30, 40]

Changing new_numbers also changes numbers because both variables reference the same list object.

To create an actual copy, Python provides the copy module.

import copy

The module provides two important functions:

copy.copy()
copy.deepcopy()

These are used for shallow and deep copying respectively.

2. What Is a Shallow Copy?

A shallow copy creates a new outer object but does not recursively copy objects contained inside it.

In other words, the outer container is duplicated, but nested objects are still shared between the original and the copied object.

A shallow copy can be created using copy.copy().

import copy

numbers = [10, 20, 30]

new_numbers = copy.copy(numbers)

print(numbers)
print(new_numbers)

Output:

[10, 20, 30]
[10, 20, 30]

The two lists are separate objects.

If we modify the outer list:

new_numbers.append(40)

print(numbers)
print(new_numbers)

Output:

[10, 20, 30]
[10, 20, 30, 40]

The original list remains unchanged.

This happens because copy.copy() created a new outer list.

3. Shallow Copy with Nested Objects

The difference becomes important when the object contains another mutable object.

Consider:

import copy

students = [
    ["John", 20],
    ["Sarah", 22]
]

new_students = copy.copy(students)

The outer lists are different, but the inner lists are shared.

We can demonstrate this using the is operator:

print(students is new_students)

Output:

False

The outer lists are different.

However:

print(students[0] is new_students[0])

Output:

True

The first inner list is the same object in both structures.

Therefore, modifying an inner object can affect the original.

new_students[0][1] = 25

print(students)
print(new_students)

Output:

[['John', 25], ['Sarah', 22]]
[['John', 25], ['Sarah', 22]]

Although new_students was copied, the nested list was not copied.

This is the key characteristic of a shallow copy.

4. What Is a Deep Copy?

A deep copy creates a completely independent copy of an object, including the objects contained inside it.

Python's copy.deepcopy() function is used for this purpose.

import copy

students = [
    ["John", 20],
    ["Sarah", 22]
]

new_students = copy.deepcopy(students)

Now both the outer list and the nested lists are independent.

print(students is new_students)

Output:

False

And:

print(students[0] is new_students[0])

Output:

False

The nested list is also a different object.

Therefore, modifying the nested data does not affect the original.

new_students[0][1] = 25

print(students)
print(new_students)

Output:

[['John', 20], ['Sarah', 22]]
[['John', 25], ['Sarah', 22]]

This makes deep copying useful when a completely independent structure is required.

5. Difference Between Shallow Copy and Deep Copy

The fundamental difference is how nested objects are handled.

Feature Shallow Copy Deep Copy
Creates a new outer object Yes Yes
Copies nested objects No Yes
Nested mutable objects are shared Usually yes No
Function copy.copy() copy.deepcopy()
Memory usage Generally lower Generally higher
Copying speed Generally faster Generally slower
Suitable for deeply nested mutable data May require caution More suitable

A shallow copy copies only the first level of the object, whereas a deep copy recursively copies the objects contained within it.

6. Shallow Copy Using Other Techniques

Python provides several ways to make shallow copies of common objects.

For lists, slicing can be used:

numbers = [10, 20, 30]

new_numbers = numbers[:]

The list() constructor can also create a shallow copy:

new_numbers = list(numbers)

The copy() method is another option:

new_numbers = numbers.copy()

For example:

numbers = [10, 20, 30]

copy1 = numbers[:]
copy2 = list(numbers)
copy3 = numbers.copy()

All three create new outer lists.

However, with nested mutable objects, these methods still perform shallow copying.

7. Example with a Nested Dictionary

Consider a dictionary containing another dictionary:

import copy

employee = {
    "name": "John",
    "address": {
        "city": "Bengaluru",
        "country": "India"
    }
}

new_employee = copy.copy(employee)

The outer dictionaries are different:

print(employee is new_employee)

Output:

False

But the nested dictionary is shared:

print(employee["address"] is new_employee["address"])

Output:

True

Therefore:

new_employee["address"]["city"] = "Mysuru"

will also change the nested data in employee.

print(employee)

Output:

{
    'name': 'John',
    'address': {
        'city': 'Mysuru',
        'country': 'India'
    }
}

Using deep copy avoids this problem:

new_employee = copy.deepcopy(employee)

new_employee["address"]["city"] = "Mysuru"

Now the original dictionary remains independent.

8. Mutable and Immutable Objects

Understanding mutable and immutable objects helps explain why shallow and deep copying matter.

Common mutable objects include:

  • Lists

  • Dictionaries

  • Sets

  • User-defined objects whose attributes can change

Common immutable objects include:

  • Integers

  • Floats

  • Strings

  • Tuples containing only immutable objects

  • Booleans

If an object contains only immutable values, the difference between shallow and deep copying may not be noticeable.

For example:

import copy

numbers = [10, 20, 30]

shallow = copy.copy(numbers)
deep = copy.deepcopy(numbers)

Since the elements are integers, there is no mutable nested object to cause the typical shared-reference problem.

The distinction becomes much more important with structures such as:

data = [
    [1, 2],
    [3, 4]
]

Here, the inner lists are mutable.

9. Copying Custom Objects

Shallow and deep copying can also be used with objects created from classes.

import copy

class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

student1 = Student("John", [80, 90, 85])

Create a shallow copy:

student2 = copy.copy(student1)

The objects themselves are different:

print(student1 is student2)

Output:

False

But their marks lists are shared:

print(student1.marks is student2.marks)

Output:

True

Therefore:

student2.marks.append(95)

can also affect student1.marks.

A deep copy creates an independent nested list:

student3 = copy.deepcopy(student1)

Now:

print(student1.marks is student3.marks)

Output:

False

10. Why Deep Copy Can Use More Memory

Deep copying recursively creates new objects.

Suppose a data structure contains thousands of nested dictionaries and lists. A deep copy may need to create a large number of additional objects.

For example:

large_data = {
    "students": [
        {"name": "John", "marks": [80, 85, 90]},
        {"name": "Sarah", "marks": [88, 91, 95]}
    ]
}

Using:

copy.deepcopy(large_data)

creates independent copies of the nested structures.

This can consume significantly more memory than a shallow copy.

Therefore, deep copy should be used when independent nested data is actually required.

11. When Should You Use Shallow Copy?

A shallow copy is useful when:

  • You only need a new outer container.

  • The nested objects should remain shared.

  • The nested objects are immutable.

  • You want to avoid unnecessary memory usage.

  • You understand which objects are shared between the original and copy.

For example, if you have a list of immutable values:

numbers = [1, 2, 3, 4, 5]
new_numbers = numbers.copy()

a shallow copy is generally sufficient.

12. When Should You Use Deep Copy?

Deep copying is useful when:

  • The data contains nested mutable objects.

  • The copied structure must be completely independent.

  • Changes to nested objects should not affect the original.

  • You are creating an independent version of a complex configuration or data structure.

Example:

import copy

original = {
    "settings": {
        "theme": "dark",
        "language": "English"
    }
}

backup = copy.deepcopy(original)

backup["settings"]["theme"] = "light"

print(original["settings"]["theme"])

Output:

dark

The original remains unchanged.

13. Important Point About Assignment

A common mistake is to confuse assignment with copying.

a = [1, 2, 3]
b = a

This does not create a copy.

Both variables refer to the same list.

print(a is b)

Output:

True

With a shallow copy:

b = copy.copy(a)

we get:

False

for the identity comparison.

With a deep copy:

b = copy.deepcopy(a)

we also get:

False

The major distinction between shallow and deep copying becomes visible when nested mutable objects are involved.

14. Important Considerations with Deep Copy

Deep copying is powerful, but it should not automatically be used everywhere.

Some objects have special behavior when copied. Objects involving external resources, such as open files, sockets, database connections, or other system resources, may not be suitable for ordinary deep copying.

Deep copying can also be unnecessarily expensive for large data structures.

Another important consideration is that deep copying can preserve relationships between objects within the copied structure. Python's deepcopy() maintains a memoization mechanism to avoid repeatedly copying the same object and to handle certain circular references.

For example, a structure can contain a reference to itself:

data = []
data.append(data)

A properly performed deep copy can handle such recursive structures without endlessly copying the same reference.

15. Practical Example

Consider a company's employee records:

import copy

employees = {
    "employee1": {
        "name": "John",
        "skills": ["Python", "SQL"]
    },
    "employee2": {
        "name": "Sarah",
        "skills": ["Java", "Python"]
    }
}

Suppose you want to create a completely separate version for testing.

Using:

test_employees = copy.copy(employees)

does not completely isolate the nested dictionaries and lists.

Instead:

test_employees = copy.deepcopy(employees)

creates an independent nested structure.

You can then modify:

test_employees["employee1"]["skills"].append("Django")

without changing:

employees["employee1"]["skills"]

This is one of the practical situations where deep copying can prevent unexpected changes to production or source data.

16. Summary

Shallow copy and deep copy are two different approaches to duplicating objects in Python.

A shallow copy creates a new outer object but keeps references to the original nested objects. It is usually faster and consumes less memory, but modifications to shared nested mutable objects can affect both structures.

A deep copy recursively creates copies of nested objects, producing a much more independent structure. It is useful when changes made to the copied data must never affect the original nested data, although it can require more processing time and memory.

The most important distinction can be remembered as:

Shallow Copy:
New outer object + shared nested objects

Deep Copy:
New outer object + new nested objects

Python provides these operations through the copy module:

import copy

shallow = copy.copy(original)
deep = copy.deepcopy(original)

Knowing when objects are shared and when they are independently copied is essential for writing reliable Python programs, particularly when working with nested data structures and mutable objects.