What's the difference between a shallow copy and a deep copy?
Quick Answer
A **shallow copy** (`list(x)`, `x.copy()`, `copy.copy(x)`) creates a new outer container but reuses references to the *same* nested/inner objects — mutating a nested object through the copy affects the original too. A **deep copy** (`copy.deepcopy(x)`) recursively copies every nested object as well, so the copy is fully independent of the original, at the cost of more time and memory.
Detailed Answer
Shallow copy: new outer container, shared inner objects
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original) # or: original[:] / list(original)
shallow.append([5, 6]) # doesn't affect original -- outer list is new
original # [[1, 2], [3, 4]]
shallow[0].append(99) # mutates the SHARED inner list!
original # [[1, 2, 99], [3, 4]] -- original changed too!
shallow is a genuinely new list object, but its elements are the same
inner list objects as original's — appending to the outer copy doesn't
touch the original, but mutating a shared inner list does, since both
original[0] and shallow[0] point at the identical object.
Deep copy: fully independent
deep = copy.deepcopy(original)
deep[0].append(100)
original # unaffected -- deep copy recursively copied every nested list too
copy.deepcopy recursively copies every object reachable from the top,
building an entirely independent structure — safe to mutate at any depth
without affecting the original, at the cost of recursively copying (and
therefore more time/memory, and needing to handle cycles, which
deepcopy does via a memo dict to avoid infinite recursion).
Which one for which types
d = {"a": [1, 2]}
d.copy() # shallow -- new dict, same inner list object
copy.deepcopy(d) # deep -- new dict AND a new inner list
t = (1, [2, 3])
copy.copy(t) # shallow -- new tuple, same inner list
Most built-in containers offer a .copy() method (or slicing [:]) that
performs a shallow copy; there's no built-in shortcut for a deep copy
— copy.deepcopy is always the tool for that.
When shallow is fine, and when it isn't
Shallow copy is fine (and cheaper) when the container only holds
immutable elements (int, str, tuple of immutables) — there's no
"shared inner object" risk because nothing can mutate them in place. It
becomes a real bug source specifically when elements are themselves
mutable (nested lists/dicts/objects) and you need the copy to be fully
independent.
Interview-ready summary: Shallow copy duplicates only the top-level
container; nested mutable objects are still shared with the original.
Deep copy recursively duplicates everything reachable, giving full
independence at higher cost. Reach for shallow copy (or plain slicing)
when contents are immutable or sharing is intentional; reach for
deepcopy when you need a completely independent structure.