← Back to Python

deepcopy vs Shallow Copy

PythonMidpython

The Question

What is the difference between deepcopy and shallow copy?

What a Strong Answer Covers

  • shallow = new container
  • same nested
  • "deep = recursive independent
  • "assignment = reference only

Senior-Level Answer

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.

Key Differences

AspectAssignmentShallow CopyDeep Copy
New top-level objectNoYesYes
New nested objectsNoNoYes
Mutation isolation (top level)NoneYesYes
Mutation isolation (nested)NoneNoYes
PerformanceO(1)O(n) top-levelO(n) full graph
Cycle handlingN/AN/Amemo dict
Methodb = acopy.copy() / [:]copy.deepcopy()

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Correctly explains that shallow copy shares nested references while deep copy duplicates the entire graph, with a concrete mutation example showing the difference.

3/3 — Strong Answer

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.

Common Mistakes

  • Saying shallow copy 'copies one level' without clarifying that nested objects are shared references
  • Not knowing that deepcopy handles cycles via memo dict — claiming it would infinite-loop
  • Recommending deepcopy as default without discussing its performance cost
  • Forgetting that list slicing b = a[:] is equivalent to a shallow copy, not a deep copy

Follow-Up Questions

  • How does deepcopy handle an object graph with circular references? — The memo dict maps id(original) -> copy. Before copying an object, it checks if it's already in memo and returns the existing copy if so.
  • When would you implement __deepcopy__ on a custom class? — When your object holds external resources (sockets, file handles) that shouldn't be duplicated, or when identity matters (singleton-like objects).
  • What happens when you try to deepcopy a threading.Lock? — Locks are copied as new, unlocked locks — state is not preserved. This can silently change behavior if the original was locked.
  • How would you deep copy a dict of lists efficiently without copy.deepcopy? — json.loads(json.dumps(d)) for pure JSON-serializable data — much faster than deepcopy for simple structures. Limitation: no custom objects.

Related Questions

  • GIL — What It Is and What It Protects
  • ThreadPoolExecutor & ProcessPoolExecutor
  • Decorators — Under the Hood
  • Generators & yield
  • Context Managers

Can You Explain This Cold?

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
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout