What is the difference between a shallow copy and a deep copy in Python?
A shallow copy creates a new outer container but keeps references to the same inner objects, so changing a nested list affects both. A deep copy recursively copies everything inside, so the two are fully independent. Use copy.copy() for shallow and copy.deepcopy() for deep.
Explain it simply
Suppose your friend has a folder of printed class notes, and some pages are just slips saying "see the shared Google Doc". A shallow copy is photocopying the folder: you get your own folder and your own printed pages, but the slips still point to the same Google Doc. If someone edits that doc, both of you see it. A deep copy is printing out the Google Doc too, so you have your own version of everything.
In Python, the "slips" are references to nested objects like inner lists or dicts.
Example
import copy
marks = [[80, 75], [60, 90]] # a list of lists
alias = marks # not a copy at all, same object
shallow = copy.copy(marks) # or marks[:] or list(marks) or marks.copy()
deep = copy.deepcopy(marks)
marks[0][0] = 99 # change a nested value
print(alias) # [[99, 75], [60, 90]] same object
print(shallow) # [[99, 75], [60, 90]] inner lists are shared
print(deep) # [[80, 75], [60, 90]] fully independent
marks.append([50, 50]) # change the outer list
print(shallow) # [[99, 75], [60, 90]] outer list is its ownHow it works
- Assignment (
b = a) copies nothing. Both names point to the same object. - Shallow copy builds a new container and fills it with the same references the original had. Adding or removing items in one container does not affect the other, but the nested objects are shared.
- Deep copy walks through the object and copies every nested mutable object it finds. It keeps a memo dictionary of objects already copied, so it handles self-referencing structures without looping forever and keeps shared references shared inside the copy.
For a list of only immutable items, like [1, 2, 3] or a list of strings, a shallow copy is enough, because nothing inside can change.
Real-world use
- Making a backup of a configuration dict before changing it for a test.
- Game boards and grids, such as a tic-tac-toe or Sudoku board stored as a list of lists, where you want to try a move without changing the real board.
- Undo features that save snapshots of nested state.
- Avoiding bugs when a function receives a nested dict and should not modify the caller's data.
Common mistakes
- Thinking
b = amakes a copy. - Using
marks[:]ordict.copy()on nested data and being surprised when inner changes leak through. - Creating a grid with
[[0] * 3] * 3. The outer* 3repeats the same inner list three times, so settinggrid[0][0] = 1changes the first item of every row. Use[[0] * 3 for _ in range(3)]instead. - Using
deepcopyeverywhere "to be safe". It is much slower on big structures and is not needed for flat lists of immutable values.
What to say in the interview
"Assignment just creates another name for the same object. A shallow copy, like list.copy(), slicing or copy.copy(), creates a new outer container, but the items inside are the same objects, so changes to a nested list show up in both. A deep copy with copy.deepcopy() recursively copies every nested object, so the two are completely independent. For a flat list of numbers or strings a shallow copy is fine; for nested mutable data I use a deep copy."
Follow-up questions
- What does
[[0] * 3] * 3produce, and what goes wrong with it? - How does
deepcopyhandle an object that contains itself? - How can a class control how it is copied?
- Is slicing a list a shallow or a deep copy?
Go deeper
Read these once the basics above feel easy.
A class can customise copying by defining __copy__ and __deepcopy__(self, memo). This is useful when an object holds something that should not be duplicated, like a database connection or a file handle.
deepcopy does not copy immutable atoms such as ints and strings; it reuses them, since they can never change. Tuples are only copied if something inside them needs copying.