What is the difference between deepcopy and shallow copy?
Copying in Python has three distinct levels: assignment, shallow copy, and deep copy. Choosing the wrong one causes aliasing bugs that are difficult to debug.
**Assignment (`b = a`)** creates no copy at all. Both names refer to the same object. Any mutation through either name is visible through the other.
**Shallow copy** creates a new top-level container object but populates it with references to the same child objects. For a list, `b = a.copy()` or `b = a[:]` creates a new list, but the elements inside are not copied — they are shared.
```python import copy
a = [[1, 2], [3, 4]] b = copy.copy(a) # shallow b.append([5, 6]) # only affects b b[0].append(99) # affects a[0] too — shared reference print(a) # [[1, 2, 99], [3, 4]] ```
Shallow copy is O(n) where n is the top-level size. It is appropriate when the elements themselves are immutable (lists of strings, lists of ints) or when you intentionally want to share the nested objects.
**Deep copy** (`copy.deepcopy(a)`) recursively traverses the entire object graph, creating new copies of every nested object. Mutations to any level of `b` do not affect `a`.
```python b = copy.deepcopy(a) b[0].append(99) print(a) # [[1, 2], [3, 4]] — unaffected ```
Deep copy handles cycles via a memo dict (the second optional argument) that tracks already-copied objects, preventing infinite recursion.
Deep copy is significantly more expensive — O(n) across the entire object graph, with overhead for memo tracking and object reconstruction. It also requires objects to be copyable; some objects (open file handles, database connections, locks) cannot be meaningfully deep-copied and will raise or produce incorrect results.
**Custom behavior:** Classes can control copy behavior by implementing `__copy__` and `__deepcopy__` methods. This is important for objects with external resources or identity requirements.
**Practical guidance:** Use shallow copy when your container holds immutable elements or shared references are intentional. Use deep copy when you need true independence between the copy and the original — for example, when passing a mutable config dict to a function that might modify it, or when implementing undo/redo with state snapshots. Always profile deep copy on large object graphs — it can be a performance bottleneck.
| Aspect | Assignment | Shallow Copy | Deep Copy |
|---|---|---|---|
| New top-level object | No | Yes | Yes |
| New nested objects | No | No | Yes |
| Mutation isolation (top level) | None | Yes | Yes |
| Mutation isolation (nested) | None | No | Yes |
| Performance | O(1) | O(n) top-level | O(n) full graph |
| Cycle handling | N/A | N/A | memo dict |
| Method | b = a | copy.copy() / [:] | copy.deepcopy() |
Correctly explains that shallow copy shares nested references while deep copy duplicates the entire graph, with a concrete mutation example showing the difference.
Covers assignment vs shallow vs deep, shows mutation behavior at each level, explains the memo dict for cycle handling, discusses performance trade-offs, and knows __copy__/__deepcopy__ hooks.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment