Python shallow copy vs deep copy Explained
When you copy data in Python, the result is not always fully independent from the original.
This is especially important with nested data, such as:
- a list inside another list
- a dictionary containing lists
- a list of dictionaries
The difference between shallow copy and deep copy is about what gets copied and what stays shared.
If you get this wrong, changing one object can unexpectedly change another.
Quick example
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0].append(99)
print(original) # [[1, 2, 99], [3, 4]]
print(shallow) # [[1, 2, 99], [3, 4]]
print(deep) # [[1, 2], [3, 4]]
Use copy.copy() for a shallow copy and copy.deepcopy() when you need fully independent nested objects.
What this page helps you understand
- What a copy means in Python
- How shallow copy works
- How deep copy works
- Why nested objects cause confusion
- When to use each approach
What a shallow copy does
A shallow copy creates a new outer object, but it does not fully copy the nested objects inside it.
That means:
- the outer container is new
- inner lists or dictionaries may still be shared
- changing a nested mutable object can affect both versions
Example:
original = [[1, 2], [3, 4]]
copied = original.copy()
copied.append([5, 6])
print(original) # [[1, 2], [3, 4]]
print(copied) # [[1, 2], [3, 4], [5, 6]]
In this case, appending a new top-level item only changes copied, because the outer list is different.
But now look at a nested change:
original = [[1, 2], [3, 4]]
copied = original.copy()
copied[0].append(99)
print(original) # [[1, 2, 99], [3, 4]]
print(copied) # [[1, 2, 99], [3, 4]]
Both changed because original[0] and copied[0] point to the same inner list.
If you are new to this, it helps to first understand mutability in Python.
What a deep copy does
A deep copy creates a new outer object and also recursively copies the nested objects inside it.
That means:
- the outer container is new
- nested mutable objects are also new
- changes inside nested structures do not affect the original
Example:
import copy
original = [[1, 2], [3, 4]]
copied = copy.deepcopy(original)
copied[0].append(99)
print(original) # [[1, 2], [3, 4]]
print(copied) # [[1, 2, 99], [3, 4]]
Here, the inner list was copied too, so changing copied[0] does not change original[0].
Why beginners get confused
This topic is confusing because copying often appears to work at first.
Common reasons:
- Simple lists of numbers often behave as expected
- Problems usually appear only with nested lists or dictionaries
- Assignment with
=does not make a copy - Some methods make shallow copies, not deep copies
For example, this does not create a copy:
original = [1, 2, 3]
copied = original
copied.append(4)
print(original) # [1, 2, 3, 4]
print(copied) # [1, 2, 3, 4]
Both names refer to the same list.
Assignment vs shallow copy vs deep copy
These three ideas are different.
Assignment
With assignment, both names point to the same object.
original = [[1, 2], [3, 4]]
assigned = original
print(original is assigned) # True
Changing one changes the other.
Shallow copy
With a shallow copy, the outer object is new, but inner objects are shared.
original = [[1, 2], [3, 4]]
shallow = original.copy()
print(original is shallow) # False
print(original[0] is shallow[0]) # True
Deep copy
With a deep copy, both the outer object and inner objects are copied.
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
print(original is deep) # False
print(original[0] is deep[0]) # False
Common ways to make a shallow copy
These all make shallow copies:
list.copy()dict.copy()set.copy()- list slicing like
my_list[:] copy.copy()from thecopymodule
Examples:
numbers = [1, 2, 3]
a = numbers.copy()
b = numbers[:]
print(a) # [1, 2, 3]
print(b) # [1, 2, 3]
For more detail, see the list.copy() method and the dict.copy() method.
When shallow copy is enough
A shallow copy is often enough when:
- your data is flat and not nested
- you only need a separate outer container
- you do not plan to change nested mutable values
Example with a flat list:
original = [1, 2, 3]
copied = original.copy()
copied.append(4)
print(original) # [1, 2, 3]
print(copied) # [1, 2, 3, 4]
This works fine because the list contains integers, and integers are immutable.
If you just want the common ways to duplicate a list, see how to copy a list in Python.
When deep copy is safer
A deep copy is safer when:
- your data contains nested lists, dictionaries, or sets
- you need to modify inner values without changing the original
- you want a fully independent duplicate
Example with a dictionary containing a list:
import copy
original = {"scores": [10, 20]}
shallow = original.copy()
deep = copy.deepcopy(original)
original["scores"].append(30)
print(original) # {'scores': [10, 20, 30]}
print(shallow) # {'scores': [10, 20, 30]}
print(deep) # {'scores': [10, 20]}
The shallow copy still shares the inner list. The deep copy does not.
Important limits and caution
Keep these points in mind:
- Deep copy can be slower on large objects
- Not every object copies cleanly in every situation
- Copy only when you really need a separate object
- Immutable values like integers and strings do not cause the same shared-mutation problem
For example, strings and integers do not behave like nested lists because they cannot be changed in place.
a = 10
b = a
b = 20
print(a) # 10
print(b) # 20
This is one reason mutable vs immutable types matters when learning about copying.
Common mistakes
These mistakes cause most copy-related bugs:
- Using
=and expecting a real copy - Using
list.copy()on a nested list and expecting full independence - Using
dict.copy()on a dictionary that contains lists or dictionaries - Changing an inner list after making a shallow copy
- Not realizing the difference between mutable and immutable values
If something seems wrong, these checks can help:
print(original is copied)
print(original == copied)
print(id(original), id(copied))
print(id(original[0]), id(copied[0]))
print(type(original))
What they tell you:
ischecks whether two names refer to the same object==checks whether values are equalid()shows the identity of an object- comparing
id(original[0])andid(copied[0])helps detect shared inner objects type()confirms what kind of object you are copying
FAQ
Does = create a copy in Python?
No. It creates another reference to the same object.
Is list.copy() a deep copy?
No. It is a shallow copy.
When do I need deepcopy()?
Use it when your object contains nested mutable values and you want full independence.
Do strings and integers need deep copy?
Usually no. They are immutable, so changing one variable does not change the original value.
Why did changing one list change the other?
They likely share the same inner object because of assignment or a shallow copy.