Python List Copies: Why Small Changes Can Have Unexpected Results
Imagine copying a player’s inventory before updating it. You add a new item to the copy, then discover that the original inventory has changed too.
Python is behaving consistently—but understanding that behaviour requires knowing which objects your variables refer to.
Let’s explore list references and copying through a different set of examples.
1. Two names can share one inventory
player_items = ["torch", "map"]
saved_items = player_items
player_items.remove("torch")
print(player_items)
print(saved_items)
Output:
['map']
['map']
The assignment saved_items = player_items gives the existing list another name. It does not save an independent snapshot.
Both names refer to the same list, so removing an item through player_items also affects what you see through saved_items.
Create a separate outer list when you need a snapshot:
player_items = ["torch", "map"]
saved_items = player_items.copy()
player_items.remove("torch")
print(player_items)
print(saved_items)
Output:
['map']
['torch', 'map']
For this list of strings, a shallow copy provides the separation we need.
2. += and + can behave differently
These two operations look similar, but their effect on a list’s aliases differs.
Using +=
tasks = ["read"]
shared_tasks = tasks
tasks += ["practise"]
print(tasks)
print(shared_tasks)
Output:
['read', 'practise']
['read', 'practise']
For a built-in list, += extends the existing list. Since shared_tasks refers to that list too, it sees the change.
Using +
tasks = ["read"]
shared_tasks = tasks
tasks = tasks + ["practise"]
print(tasks)
print(shared_tasks)
Output:
['read', 'practise']
['read']
Here, + creates a new list. The assignment then makes tasks refer to it.
shared_tasks still refers to the original list.
Remember: For built-in lists, += modifies the existing object, while + produces a new one.
3. A function can modify the caller’s list
Consider a function that adds a participant:
def register(names):
names.append("Maya")
participants = ["Arun", "Leah"]
register(participants)
print(participants)
Output:
['Arun', 'Leah', 'Maya']
During the call, the parameter names refers to the same list as participants. The function modifies that shared list.
However, reassigning the parameter behaves differently:
def register(names):
names = names + ["Maya"]
return names
participants = ["Arun", "Leah"]
updated = register(participants)
print(participants)
print(updated)
Output:
['Arun', 'Leah']
['Arun', 'Leah', 'Maya']
The expression names + ["Maya"] creates a new list. Reassigning the local name names does not reassign participants.
The function returns the new list so the caller can use it.
4. Equal lists are not necessarily the same list
Python provides two comparisons that answer different questions:
first = [4, 8]
second = first.copy()
third = first
print(first == second)
print(first is second)
print(first is third)
Output:
True
False
True
Here is what each comparison checks:
| Expression | Question |
|---|---|
first == second |
Do these lists contain equal values? |
first is second |
Are these references to the same object? |
first is third |
Are these references to the same object? |
Use == to compare list contents. Use is when you specifically need to check object identity.
5. A copied list can still share dictionaries
Nested data does not have to contain inner lists. The same issue arises with dictionaries inside a list.
products = [
{"name": "Notebook", "stock": 12},
{"name": "Pen", "stock": 30},
]
snapshot = products.copy()
snapshot[0]["stock"] = 0
print(products[0]["stock"])
print(snapshot[0]["stock"])
Output:
0
0
The outer list was copied, but the dictionaries inside it remain shared.
For this data, a deep copy creates independent dictionaries too:
from copy import deepcopy
products = [
{"name": "Notebook", "stock": 12},
{"name": "Pen", "stock": 30},
]
snapshot = deepcopy(products)
snapshot[0]["stock"] = 0
print(products[0]["stock"])
print(snapshot[0]["stock"])
Output:
12
0
Choose the copy depth based on which mutable objects need to be independent. Deep copying can do unnecessary work when sharing nested objects is intentional.
How to reason through these examples
Before predicting the output, identify:
- Which lines create new lists.
- Which names refer to the same list.
- Which operations modify existing objects.
- Whether a copy still contains shared mutable objects.
This approach helps you explain the behaviour instead of memorising individual examples.
For more detail, see the official Python documentation on shallow and deep copying.
Free PCEP practice test
Preparing for PCEP? I create Python learning resources through AM Academy, and you can access my free practice test here: https://www.udemy.com/course/python-pcep-30-03-certification-practice-tests-2026/?couponCode=FREE-COURSE-PCEP. This is independently created preparation material and is not affiliated with or endorsed by the Python Institute.
Top comments (0)