Skip to content

copy() vs deepcopy()

copy.copy() makes a shallow copy: a new outer object, but nested objects inside it are still shared with the original. copy.deepcopy() makes a deep copy: the outer object and everything nested inside it are recursively duplicated, so nothing is shared.

Without the copy module, β€œcopying” a list with list(original) or slicing original[:] only copies one level β€” nested mutable objects are still shared, which surprises people expecting a fully independent copy:

original = [[1, 2], [3, 4]]
shallow = list(original) # new outer list...
shallow[0].append(99) # ...but the inner lists are the SAME objects
print(original) # [[1, 2, 99], [3, 4]] -- original was mutated too!
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] -- still shared, same as list(original)
deep = copy.deepcopy(original)
deep[0].append(100)
print(original) # [[1, 2, 99], [3, 4]] -- unaffected, fully independent
# The distinction matters most with nested mutable structures / custom objects
class Node:
def __init__(self, value, children=None):
self.value = value
self.children = children or []
root = Node(1, [Node(2), Node(3)])
shallow_root = copy.copy(root)
shallow_root.children.append(Node(4))
print(len(root.children)) # 3 -- the shared `children` list was mutated too
deep_root = copy.deepcopy(root)
deep_root.children.append(Node(5))
print(len(root.children)) # 3 (unchanged by the deepcopy's append) -- vs 3 above for shallow
  • copy() is fast and often enough: for a flat structure (list of numbers, dict of strings), shallow and deep copies behave identically but shallow is cheaper.
  • deepcopy() guarantees full independence: safe when you need to mutate a copy without any risk of touching the original, at any nesting depth.
  • Both are more explicit than manual copying: copy.deepcopy(x) states intent clearly, versus ad-hoc nested-comprehension copying that’s easy to get subtly wrong.
  • For a flat (one-level) list/dict of immutable values (ints, strings), copy(), deepcopy(), and list(x)/dict(x) all behave the same β€” the distinction only matters once there’s nested mutable data.
  • deepcopy() handles circular references correctly (it tracks already-copied objects), so deepcopy()-ing a structure that references itself won’t infinite-loop.
  • deepcopy() is meaningfully slower than copy() for large nested structures β€” don’t reach for it by default if a shallow copy is provably sufficient.
  • Assignment (b = a) copies nothing β€” it’s a second name for the same object. That’s a separate, more fundamental gotcha than shallow-vs-deep.
  1. After shallow = copy.copy(original), if original contains nested lists, are those nested lists shared or independent?

    AnswerShared β€” `copy()` only duplicates the outer container; nested mutable objects remain the same objects in both.
  2. When do copy() and deepcopy() produce the same practical result?

    AnswerWhen the structure is flat and contains only immutable values β€” there's nothing nested to share or duplicate differently.
  3. Does deepcopy() break on a structure that contains a reference to itself?

    AnswerNo β€” `deepcopy()` tracks objects it has already copied and reuses the copy, so circular references are handled safely rather than causing infinite recursion.