Python Shallow Copy vs Deep Copy: Why It Crashes Production and How to Avoid It
I shipped code that mutated a default config dict inside a request handler. Two hours later, a user’s session data leaked into another user’s session. Same IP, same timestamp, completely wrong data. The config had been “copied” with dict.copy(). I learned the hard way that Python’s copy semantics have three distinct levels, and most people stop at the wrong one.
The problem: = never copies
original = [[1, 2, 3], [4, 5, 6]]reference = original # just an aliasreference[0][0] = 99print(original[0][0]) # 99This is obvious when you think about it — = binds a new name to the same object. But the subtler bug comes from list.copy(), dict.copy(), or copy.copy():
import copy
original = [[1, 2, 3], [4, 5, 6]]shallow = original.copy()shallow[0][0] = 99print(original[0][0]) # 99 ← original was changed too!If you’ve been burned by this, you’re not alone. The copy() method creates a new outer container, but the inner lists are still shared references. Modify a nested element through the “copy” and you’ve just modified the original.
Three levels, one correct choice
| Level | Method | Outer object | Inner objects |
|---|---|---|---|
| Reference copy | = | Shared | Shared |
| Shallow copy | copy.copy(), .copy(), [:] | New | Shared |
| Deep copy | copy.deepcopy() | New | New copies |
Shallow copy works fine for flat structures — [1, 2, 3], {"a": 1, "b": 2}. The trouble starts when values are themselves mutable containers.
The production scenario that bit me
I was building a per-user configuration system. Each request needed a clone of the default config so users could override settings without affecting each other.
config = { "database": { "host": "localhost", "port": 5432, "credentials": {"user": "admin", "password": "secret"} }}
# ❌ Bad: shallow copy shares nested dictsuser_config = config.copy()user_config["database"]["port"] = 5433 # modifies original!Every user who hit that endpoint after user A got port 5433 instead of 5432. Worse — the credentials dict was also shared, so one user could accidentally (or intentionally) overwrite another user’s session credentials.
The fix was one import and one function call:
from copy import deepcopy
config = { "database": { "host": "localhost", "port": 5432, "credentials": {"user": "admin", "password": "secret"} }}
# ✅ Good: deep copy gives independenceuser_config = deepcopy(config)user_config["database"]["port"] = 5433 # original unchangedDeep copy for the win
import copy
original = [[1, 2, 3], [4, 5, 6]]deep = copy.deepcopy(original)deep[0][0] = 99print(original[0][0]) # 1 ← original is unchangedprint(deep[0][0]) # 99deepcopy() walks the entire object graph and creates new objects at every level. Nested lists, dicts inside dicts, objects containing other objects — all fully independent.
When NOT to deep copy
Deep copy isn’t free. It’s slower and uses more memory because it recurses into everything. For flat data, copy.copy() is fine. For large graphs, consider whether you really need full independence or if you can restructure the data to avoid the sharing problem.
I also don’t use deepcopy on objects with file handles, database connections, or other OS resources — you’ll get duplicate handles or crash. For those, write explicit __copy__ and __deepcopy__ methods that control what gets copied.
The rule of thumb
- Flat, immutable values (ints, strings, tuples of those) →
=is fine - Flat containers with immutable items →
copy.copy()or.copy() - Any nested mutable structure →
copy.deepcopy()
The worst bugs look like correct code. config.copy() reads like “make a copy” — and it does, just not a complete one. When in doubt about nesting depth, reach for deepcopy. It’s one import line and it will save you the “how did my data get corrupted?” debugging session.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments