DEV Community

Ankur Marwaha
Ankur Marwaha

Posted on

Python List Copies: Why Small Changes Can Have Unexpected Results

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)
Enter fullscreen mode Exit fullscreen mode

Output:

['map']
['map']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

['map']
['torch', 'map']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

['read', 'practise']
['read', 'practise']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

['read', 'practise']
['read']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

['Arun', 'Leah', 'Maya']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

['Arun', 'Leah']
['Arun', 'Leah', 'Maya']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

True
False
True
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

Output:

0
0
Enter fullscreen mode Exit fullscreen mode

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"])
Enter fullscreen mode Exit fullscreen mode

Output:

12
0
Enter fullscreen mode Exit fullscreen mode

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:

  1. Which lines create new lists.
  2. Which names refer to the same list.
  3. Which operations modify existing objects.
  4. 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)