Back to List

Mutable Object Reference Propagation β€” The Pitfalls of Copying

Learn why the original list changes when you copy it in Python, the difference between shallow and deep copies, and how to use the copy module.

Intermediate
|
10min
|
Verified (2026-07)
mutable objectreference propagationshallow copydeep copymutablelist copy
Progress0/18 (0%)

Mutable Object Reference Propagation: The Pitfalls of Copying

After completing this topic, you will:

Understand why copying a list using = in Python modifies the original, and be able to distinguish and use shallow and deep copies.


Copying, but the original changes?

python
original = [1, 2, 3]
copy = original
copy.append(4)
print(original) # [1, 2, 3, 4] β€” The original also changes!

copy = original is not a copy. It's simply assigning another name to the same list. original and copy are two names pointing to the same list.

In the previous topic (Variables and Memory), we learned that a variable is not a "box that holds a value," but rather an "arrow that points to an object." This is the moment where that concept becomes problematic in practice.

python
print(id(original)) # 4392957504
print(id(copy)) # 4392957504 β€” The same address!

Real Copy: Slicing

python
original = [1, 2, 3]
copy = original[:] # Full slicing = new list
copy.append(4)
print(original) # [1, 2, 3] β€” The original doesn't change
print(copy) # [1, 2, 3, 4]

[:] creates a new list by slicing the list from beginning to end. list(original) or original.copy() have the same effect.


Limitations of Shallow Copy

python
matrix = [[1, 2], [3, 4]]
shallow = matrix[:]
shallow[0][0] = 99
print(matrix) # [[99, 2], [3, 4]] β€” The original also changes!

Slicing only copies one level deep. matrix[:] creates a new outer list, but the inner lists [1, 2] and [3, 4] share the same objects. Modifying the inner lists will also affect the original.

This is a shallow copy.


Deep Copy: copy.deepcopy

python
import copy
matrix = [[1, 2], [3, 4]]
deep = copy.deepcopy(matrix)
deep[0][0] = 99
print(matrix) # [[1, 2], [3, 4]] β€” The original doesn't change

deepcopy recursively copies all inner objects as well. This creates a completely independent new object.


When to Use Which

ScenarioMethodReason
1-dimensional listlst[:] or lst.copy()Fast and simple
2-dimensional or more nested listscopy.deepcopy(lst)Need inner lists to be independent
Dictionarydict.copy() or {**d}Shallow copy (one level)
Nested dictionariescopy.deepcopy(d)Need inner dict/list to be independent
Cases where copying is unnecessaryUse = as isIntentionally share the same object

The Same Applies to Function Arguments

python
def add_item(lst, item):
lst.append(item)
return lst
my_list = [1, 2, 3]
result = add_item(my_list, 4)
print(my_list) # [1, 2, 3, 4] β€” The original changes

When you pass a list to a function, a reference is passed. Modifying the list with .append() inside the function will also change the original. To preserve the original, make a copy inside the function first:

python
def add_item_safe(lst, item):
new_lst = lst[:]
new_lst.append(item)
return new_lst

Mutable vs. Immutable Summary

TypeMutable/ImmutableEffect on Original After Copying
int, float, str, tupleImmutableNo effect (a new object is created anyway)
list, dict, setMutableOriginal also changes

Immutable objects do not have this problem because "modifying" a string always creates a new string. You only need to be careful with mutable objects.


Key Takeaway

= is not a copy, but rather assigning another name to the same object. For one-dimensional lists, use [:] or .copy(), and for nested structures, use copy.deepcopy(). When passing mutable objects to functions, the original can be modified β€” if this is not intended, make a copy first.

πŸ’¬ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...