Shallow Copy vs Deep Copy in Python Explained

In Python, duplicating an object is primarily achieved through either a shallow copy or a deep copy, and understanding the operational difference between the two is vital for preventing unexpected bugs when mutating data. While both approaches generate a new outer container object, they diverge fundamentally in how they treat nested, mutable elements. A shallow copy inserts references to the original nested objects into the new container, meaning modifications to inner items affect both instances. Conversely, a deep copy recursively duplicates every object encountered within the original structure, creating an entirely independent clone. This guide covers the mechanics of each copying method, their operational differences, and practical examples of their behavior.

The Foundation: Assignment vs. Copying

Before comparing shallow and deep copies, it is essential to distinguish them from standard variable assignment (=). Assigning a variable does not create a new object; it merely creates a new reference (or alias) bound to the existing object in memory:

original = [1, [2, 3]]
alias = original  # Both point to the exact same memory address
alias.append(4)
print(original)  # Output: [1, [2, 3], 4]

To create an actual independent container rather than a reference, Python provides the built-in copy module.

What is a Shallow Copy?

A shallow copy creates a new collection object, but populates it with references to the child objects contained in the original.

How to Create a Shallow Copy

Shallow copies can be constructed using:

Operational Behavior

Because a shallow copy creates a new container, adding or removing elements from the top-level container does not impact the original. However, because child objects are copied by reference, mutating a nested mutable item alters that item across both the original and the copied structure.

import copy

original = [1, [2, 3]]
shallow = copy.copy(original)

# Modifying the top-level list
shallow.append(4)
print(original)  # Output: [1, [2, 3]] (unaffected)
print(shallow)   # Output: [1, [2, 3], 4]

# Modifying a nested object
shallow[1].append(99)
print(original)  # Output: [1, [2, 3, 99]] (affected!)
print(shallow)   # Output: [1, [2, 3, 99], 4]

What is a Deep Copy?

A deep copy constructs a new collection object and then recursively copies all nested objects found inside it.

How to Create a Deep Copy

A deep copy is created using:

Operational Behavior

Because deep copying operates recursively, it walks the entire object graph. Every mutable child, grandchild, and nested element is duplicated at a new memory address. Consequently, changes made anywhere inside the deep-copied structure—regardless of nesting depth—will never reflect in the original object.

import copy

original = [1, [2, 3]]
deep = copy.deepcopy(original)

# Modifying a nested object
deep[1].append(99)
print(original)  # Output: [1, [2, 3]] (completely isolated)
print(deep)      # Output: [1, [2, 3, 99]]

Key Operational Differences

Feature Shallow Copy (copy.copy) Deep Copy (copy.deepcopy)
Recursion Copies only the immediate parent container. Recursively copies all child and nested objects.
Nested Object Identity Shares memory addresses (id()) of nested items. Generates new memory addresses (id()) for nested items.
Side Effects Mutating nested objects affects the original. Fully isolated; no side effects on the original.
Performance Fast execution; minimal memory overhead. Slower execution; higher memory consumption.
Cyclic References Not applicable (does not traverse children). Automatically tracked via an internal memo dictionary to prevent infinite loops.

When to Use Each