Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected
Python

Python List Copy vs Deep Copy: Avoiding Common Pitfalls

Learn the difference between shallow and deep copy in Python lists, when each is appropriate, and how to avoid bugs caused by unintended shared references in nested structures.

July 2026 8 min read 1 views 0 hearts

You've been there. You copy a list in Python, change something in the copy, and suddenly your original list is also changed. It's confusing, frustrating, and can break your code in subtle ways. Let me walk you through what's really happening and how to avoid these pitfalls.

The Shallow Copy Trap

When you write something like new_list = old_list, you're not actually creating a new list. You're just creating a new reference to the same list object. Any change you make to new_list will also affect old_list. This is the most common mistake beginners make.

original = [1, 2, 3]
copy = original
copy.append(4)
print(original)  # Output: [1, 2, 3, 4]

See what happened? You only changed copy, but original changed too. That's because both variables point to the same list in memory.

The Shallow Copy Solution

For simple lists containing only immutable objects like integers or strings, you can use the copy() method or the slicing syntax [:].

original = [1, 2, 3]
shallow_copy = original.copy()
shallow_copy.append(4)
print(original)  # Output: [1, 2, 3]
print(shallow_copy)  # Output: [1, 2, 3, 4]

This works perfectly for flat lists. But here's where it gets tricky.

When Shallow Copy Fails

Consider a list that contains other lists or mutable objects:

original = [[1, 2], [3, 4]]
shallow_copy = original.copy()
shallow_copy[0].append(5)
print(original)  # Output: [[1, 2, 5], [3, 4]]

Wait, what? You only changed the copy, but the original also changed. That's because copy() only creates a new outer list, but the inner lists are still shared between the original and the copy. This is the shallow copy behavior.

Deep Copy to the Rescue

When you need a completely independent copy, including all nested objects, you need deepcopy from the copy module.

import copy

original = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original)
deep_copy[0].append(5)
print(original)  # Output: [[1, 2], [3, 4]]
print(deep_copy)  # Output: [[1, 2, 5], [3, 4]]

Now the original stays untouched. Deep copy recursively copies everything, creating entirely independent objects at every level.

When to Use Each

Use shallow copy when: - Your list contains only immutable objects (integers, strings, tuples) - You want to share nested objects between copies - Performance matters and you're working with large nested structures

Use deep copy when: - Your list contains mutable objects like other lists, dictionaries, or custom objects - You need complete independence between copies - You're modifying nested structures and don't want side effects

Real-World Example from PythonSkillset

At PythonSkillset, we once had a bug where a configuration list was being modified unexpectedly. The code looked something like this:

default_config = [{"port": 8080, "host": "localhost"}, {"port": 9090, "host": "example.com"}]

def get_config():
    return default_config.copy()  # This is shallow!

config1 = get_config()
config2 = get_config()
config1[0]["port"] = 3000
print(config2[0]["port"])  # Output: 3000 (unexpected!)

The fix was simple: use deepcopy instead.

import copy

def get_config():
    return copy.deepcopy(default_config)

Understanding the Difference

Shallow copy creates a new list, but the elements inside are still references to the same objects. If those objects are mutable (like lists, dictionaries, or custom objects), changes will propagate.

Deep copy creates a completely independent copy at every level. Every nested object is also copied, so changes never affect the original.

Practical Examples

Example 1: Working with game state

import copy

initial_board = [["X", "O"], ["O", "X"]]
board_copy = copy.deepcopy(initial_board)
board_copy[0][0] = "O"
print(initial_board)  # Output: [['X', 'O'], ['O', 'X']] - unchanged

Example 2: Configuration dictionaries

import copy

base_config = {
    "database": {"host": "localhost", "port": 5432},
    "cache": {"enabled": True, "ttl": 300}
}

user_config = copy.deepcopy(base_config)
user_config["database"]["port"] = 5433
print(base_config["database"]["port"])  # Output: 5432 - safe!

Performance Considerations

Deep copy is slower and uses more memory because it recursively copies everything. For large nested structures, this can be significant. Use it only when you need true independence.

For simple cases, shallow copy is faster and perfectly fine. The key is knowing when your data structure contains mutable objects.

Common Pitfalls to Watch For

1. Forgetting about nested lists in function arguments

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item(1))  # Output: [1]
print(add_item(2))  # Output: [1, 2] - unexpected!

The default argument [] is created once and reused. Use None and create a new list inside the function instead.

2. Copying objects with custom classes

class User:
    def __init__(self, name, roles):
        self.name = name
        self.roles = roles

user1 = User("Alice", ["admin", "editor"])
user2 = copy.deepcopy(user1)
user2.roles.append("viewer")
print(user1.roles)  # Output: ['admin', 'editor'] - safe!

Performance Tips

Deep copy is expensive. For large nested structures, it can be slow and memory-intensive. Consider these alternatives:

  • Use copy.copy() for shallow copies when you know the structure is flat
  • Implement __copy__ and __deepcopy__ methods in your custom classes for more control
  • Use pickle for serialization if you need to copy complex object graphs

The Bottom Line

Always ask yourself: "Do I need the nested objects to be independent?" If yes, use deepcopy. If no, copy() or slicing is fine. When in doubt, deep copy is safer but slower.

Remember this simple rule: shallow copy for flat lists, deep copy for nested structures. Your future self will thank you when debugging those mysterious bugs.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.