← Python Code Memory & Data Structures
Browse Python Concepts

Shallow Copy vs Deep Copy — The Nested List Trap

Mental Model

A shallow copy is like taking a photograph of a bookshelf: it captures the references to the books, but not the contents of the books themselves. If you change a page in a book, both the original and the photo still point to the same, now modified, book.

Rule: When copying collections containing nested mutable structures, always use copy.deepcopy or structure-specific copy methods to prevent data corruption.

The Setup

You are implementing a transaction rollback mechanism in a finance module. You shallow copy a matrix of transaction legs to store a baseline, then modify the current state. If validation fails, you expect to restore the pristine baseline.

What Does This Print?

Broken code
Python
import copy

# Nested data representing multiple accounts and their adjustment amounts
accounts_state = [
    {"account": "acc_1", "adjustments": [100, -50]},
    {"account": "acc_2", "adjustments": [200]}
]

# Create a backup using copy.copy()
backup_state = copy.copy(accounts_state)

# Apply pending adjustments
accounts_state[0]["adjustments"].append(-10)

# Check if backup was protected
print(f"Original first adjustments: {accounts_state[0]['adjustments']}")
print(f"Backup first adjustments: {backup_state[0]['adjustments']}")
Predict whether the backup_state is preserved or if it contains the updated transaction adjustments.

The Output

What actually happens
Original first adjustments: [100, -50, -10] Backup first adjustments: [100, -50, -10]

The backup was modified. Even though accounts_state and backup_state are different outer list objects with unique memory addresses, the dictionaries inside them, and the lists inside those dictionaries, were not copied. Both outer lists contain references to the exact same inner objects.

Why Python Does This

copy.copy() performs a shallow copy. It creates a new object of the same type and populates it with references to the items found in the original. It does not recursively clone nested components. If an item inside the container is mutable (like a dictionary or a list), mutations to that item will propagate to both the copy and the original, because both reference the identical child object in the CPython heap.

The Fix

Corrected pattern
Python
import copy

accounts_state = [
    {"account": "acc_1", "adjustments": [100, -50]},
    {"account": "acc_2", "adjustments": [200]}
]

# Use deepcopy to recursively clone all elements in nested containers
backup_state = copy.deepcopy(accounts_state)

accounts_state[0]["adjustments"].append(-10)
# The backup remains completely untouched

copy.deepcopy() recursively traverses the entire object graph, creating new, independent copies of all mutable objects encountered, including those nested within other containers. This ensures no shared references exist between the original and the copy.

How This Fails in Real Systems

A game engine saved state history using shallow copies of the player map grid. During high-stakes matches, when players upgraded items inside their storage bags (nested arrays), the items updated across historical logs. This allowed a duplication exploit that crashed the virtual economy, remaining undetected for three weeks.

Key Takeaway

When copying collections containing nested mutable structures, always use copy.deepcopy or structure-specific copy methods to prevent data corruption.
Common mistake: Developers assume that copy.copy() creates a fully independent replica of a complex data structure, including all its nested mutable elements.