Python copy Module — Core Concepts
The problem copy solves
Python variables are references — names that point to objects in memory. Assignment (b = a) creates another reference to the same object, not a duplicate. For immutable types (int, str, tuple of immutables), this is fine. For mutable types (list, dict, set, custom objects), it creates shared-state bugs.
original = [[1, 2], [3, 4]]
alias = original
alias[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] — surprise!
Shallow copy: copy.copy()
A shallow copy creates a new container but fills it with references to the same child objects:
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
shallow.append([5, 6])
print(original) # [[1, 2], [3, 4]] — outer list unaffected ✅
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] — inner list still shared ❌
The outer structure is independent, but nested mutable objects remain linked.
Other ways to shallow copy
Python offers several shallow-copy shortcuts:
list(original)ororiginal[:]for listsdict(original)ororiginal.copy()for dictsset(original)ororiginal.copy()for sets
All of these are equivalent to copy.copy() for their respective types.
Deep copy: copy.deepcopy()
A deep copy recursively duplicates everything — the container and every object nested inside it:
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]] — completely independent ✅
When to use deep copy
- You have nested mutable structures (list of lists, dict of dicts)
- You’re creating snapshots of state (undo systems, test fixtures)
- You’re passing data to functions that might modify it and you want to preserve the original
When shallow copy is enough
- Flat collections of immutable items (list of strings, set of numbers)
- You only modify the outer container, never the nested elements
- Performance matters and you understand the sharing
How deep is deep?
deepcopy handles:
- Nested structures — lists inside dicts inside lists, any depth
- Circular references — it tracks already-copied objects with a memo dict to avoid infinite loops
- Shared internal objects — if two items in the original reference the same object, the deep copy preserves that sharing pattern in the copy
import copy
shared = [1, 2, 3]
original = [shared, shared] # both entries point to same list
deep = copy.deepcopy(original)
deep[0].append(99)
print(deep[1]) # [1, 2, 3, 99] — sharing preserved in the copy
print(original[0]) # [1, 2, 3] — original untouched
Common misconception
People sometimes think deepcopy is always the “safe” choice and should be used everywhere. In practice, deep copy is significantly slower than shallow copy — it has to traverse the entire object graph. For large data structures, this can be expensive. Choose the right level of copying for your situation; don’t default to deepcopy “just to be safe.”
Quick reference
| Operation | New container? | New nested objects? | Speed |
|---|---|---|---|
Assignment (b = a) | ❌ | ❌ | Instant |
| Shallow copy | ✅ | ❌ | Fast |
| Deep copy | ✅ | ✅ | Slower |
One thing to remember
Shallow copy is fast and handles most cases where you only modify the outer structure. Deep copy is the nuclear option for when you need total independence — use it deliberately, not as a default.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.
- Python Datetime Handling Why dealing with dates and times in Python is trickier than it sounds — and how the datetime module tames the chaos