Skip to Content
Course content

45: Copying Collections: Shallow vs Deep Copy

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I can almost guarantee that at some point, you've written a line of code like new_list = old_list and assumed you just created a backup of your data. It feels intuitive. In your head, you're thinking, "I'm taking the values from here and putting them over there." But in Python, that is fundamentally not what is happening.

The Assignment Operator is not a Copy

When you use the equals sign with a collection (like a list or a dictionary), you aren't copying the data. You're just creating a new name that points to the exact same spot in your computer's memory. I call this "aliasing."

# The "Oh no" moment
original_profile = ["Alice", "Developer", ["Python", "Rust"]]
backup_profile = original_profile

# Let's change the backup
backup_profile[0] = "Bob"

print(original_profile) 
# Output: ["Bob", "Developer", ["Python", "Rust"]]

Notice how Alice vanished? Because original_profile and backup_profile are just two different labels for the same object. If you change one, you change the other. This is a classic source of bugs that can haunt a codebase for weeks if you aren't looking for it.

Shallow Copies only go skin deep

To actually create a separate object, you can use the .copy() method or the list() constructor. This is what we call a shallow copy. It creates a new collection object, but it doesn't recursively copy the objects inside that collection.

For a simple list of strings or numbers, this works perfectly. But as soon as you have "nested" collections (like a list inside a list), you're back in danger.

# Trying to be safer with a shallow copy
original_profile = ["Alice", "Developer", ["Python", "Rust"]]
backup_profile = original_profile.copy()

# Changing a top-level element is now safe
backup_profile[0] = "Bob"
print(original_profile[0]) # Still "Alice" - Success!

# But wait... let's change the nested list
backup_profile[2].append("Go")
print(original_profile[2]) # Output: ["Python", "Rust", "Go"]

What happened? The shallow copy created a new outer list, but the inner list (the skills list) was still just a reference to the original. The "skin" was copied, but the "guts" were shared. Honestly, this is where most Python developers get tripped up. It's a subtle distinction, but a critical one.

Deep Copying for fully independent data

If you need a total disconnect—where no matter how deep the nesting goes, the new object is entirely independent—you need the copy module and its deepcopy() function. This tells Python to walk through the entire structure and recursively copy every single object it finds.

import copy

original_profile = ["Alice", "Developer", ["Python", "Rust"]]
backup_profile = copy.deepcopy(original_profile)

# Now we can be as destructive as we want
backup_profile[2].append("Go")
backup_profile[0] = "Bob"

print(original_profile) 
# Output: ["Alice", "Developer", ["Python", "Rust"]]
# Everything is preserved.

A word of caution: deepcopy is significantly slower and uses more memory than a shallow copy because it has to do so much more work under the hood. Don't use it by default; use it when you actually have nested mutable data that needs to be isolated.




📋 Practical Task

Fixing the Game State Undo Bug

You are building a simple RPG. You've implemented an "Undo" feature that saves the current game state before the player makes a move. However, you've discovered a bug: when the player gains an item in the current game, that item also magically appears in the "undo" backup, making the undo feature useless.

Your Task: Modify the save_game_state function below to ensure the backup is a completely independent copy of the state, so that modifications to the current_state do not affect the undo_stack.

import copy

# The current state of the game
# Format: { "player_name": str, "inventory": list }
current_state = {
    "player_name": "Valerius",
    "inventory": ["Rusty Sword", "Health Potion"]
}

undo_stack = []

def save_game_state(state):
    # BUG: This currently performs a shallow copy (or assignment)
    # Fix this line to ensure the inventory list is also copied
    backup = state.copy() 
    undo_stack.append(backup)

# --- Test Sequence ---
save_game_state(current_state)

# Player finds a legendary item
current_state["inventory"].append("Dragon Scale Shield")

# Check if the backup was corrupted
if "Dragon Scale Shield" in undo_stack[0]["inventory"]:
    print("Bug still exists: The backup was modified!")
else:
    print("Success: The backup is independent!")
Rating
0 0

There are no comments for now.

to be the first to leave a comment.