Learn · Run · Break · Fix
A learning series from A Wandering Mind
Eighteen interlinked lessons on strings, lists, files, exceptions, and dictionaries—with original explanations, runnable examples, real output, and supporting CPSC 1301K lectures.
>>> guide = { ... "lessons": 18, ... "modules": 5, ... "python": "3.10+" ... } >>> guide["goal"] = "understand it"
References, Aliasing, and Copying Lists in Python
A variable that refers to a list does not contain a private copy of every item. It refers to a list object. Assigning that variable to another name can create an alias—two names pointing to the same mutable list—while slicing or copy() can create a separate outer list. Understanding that distinction explains many surprising list changes before they become bugs.
Variables refer to objects
When Python evaluates a list literal such as ["red", "green", "blue"], it creates a list object. A variable name such as first refers to that object. This distinction may feel abstract at first, but it becomes visible as soon as a second variable is assigned from the first.
If you write second = first, Python does not automatically build another list with the same contents. Instead, both names refer to the same list object. Because lists are mutable, changing the list through either name changes what both names appear to contain.
first = ["red", "green", "blue"]
second = first
second[0] = "orange"
print(first)
print(second)
print(first is second)
['orange', 'green', 'blue']
['orange', 'green', 'blue']
True
The mutation happens through second, but first shows the same changed list because there is only one list object involved. The expression first is second returns True because is tests object identity: it asks whether both references point to the very same object.
Aliasing: two or more variable names refer to the same mutable object. With a list, mutation through one alias is visible through the others because the underlying object is shared.
Equality and identity answer different questions
Python’s == operator and is operator are not interchangeable. For lists, == asks whether the contents compare as equal. is asks whether the two expressions refer to the same object. Two separate lists can contain equal values while still having different identities.
This is why identity is useful for demonstrating aliases and copies. A copied list may print exactly like the original and compare equal with ==, but original is copied should be False when the outer list was actually copied.
A full slice can create a separate outer list
In the previous lesson, you learned that a slice returns a list. A full slice—original[:]—selects all of the elements, which makes it a simple way to create another outer list containing references to the same elements. For a flat list of strings, numbers, or other immutable values, that often behaves like the independent copy beginners expect.
original = ["north", "south", "east"]
copied = original[:]
copied[1] = "center"
print(original)
print(copied)
print(original is copied)
['north', 'south', 'east']
['north', 'center', 'east']
False
Here, changing index 1 in copied does not alter the outer list named original. The identity test confirms that the two variables refer to different list objects. The lists started with equal contents, but they can now diverge independently at the outer level.
The copy() method expresses the intent directly
Python lists also provide a copy() method. For a flat list, backup = scores.copy() has the same broad effect as backup = scores[:]: Python creates a new outer list containing the same element references. Many programmers prefer copy() because the purpose is explicit when reading the code.
scores = [88, 91, 95]
backup = scores.copy()
scores.append(100)
print(scores)
print(backup)
[88, 91, 95, 100]
[88, 91, 95]
Appending to scores changes only that outer list. backup remains three items long. This is a practical pattern when you want to preserve the current sequence before making later changes. You could also use list(scores) to create a new list from an existing iterable, but copy() communicates the specific intention clearly when the source is already a list.
Copies are shallow unless you deliberately go deeper
The word copy needs one important qualification. A full slice and list.copy() create a shallow copy. The outer list is new, but Python does not recursively duplicate every mutable object nested inside it. If the elements include inner lists, both outer lists can still refer to the same inner list objects.
grid = [[1, 2], [3, 4]]
copy_grid = grid.copy()
copy_grid[0][0] = 99
print(grid)
print(copy_grid)
print(grid is copy_grid)
print(grid[0] is copy_grid[0])
[[99, 2], [3, 4]]
[[99, 2], [3, 4]]
False
True
The outer identity test is False, so grid and copy_grid are different outer lists. But their first elements are the same inner list object, so grid[0] is copy_grid[0] is True. Mutating that shared inner list therefore appears through both outer structures.
When a program truly needs recursively independent nested objects, the standard library’s copy module provides copy.deepcopy(). Deep copying deserves care because objects can have more complicated relationships than a simple nested list. For this lesson, the essential rule is simpler: list.copy() and [:] are shallow.
b = aBoth names refer to the same list object.
b = a[:]Creates a new outer list.
b = a.copy()Also creates a shallow outer-list copy.
a is bTests whether both references point to the same object.
When should you intentionally use an alias?
Aliasing is not automatically a mistake. Sometimes a program deliberately wants several parts of the code to work with the same shared list. Passing a list into a function, storing the same object in multiple places, or giving a meaningful second name to shared state can all be legitimate designs.
The problem begins when code expects independence but accidentally creates sharing. If you intend to preserve a snapshot and then mutate the working list, an alias is the wrong choice. If you intend every reference to see the same changes, copying may be unnecessary. The important skill is knowing which relationship your program needs before you choose the assignment or copy operation.
Common mistakes with references and copies
Some reference mistakes do not raise exceptions at all—they simply change more data than the programmer expected. The examples below focus on genuine runtime errors related to copy operations and mutation. Each traceback comes from an executed failing example.
items = ["a", "b"]
copy = items.copy
copy.append("c")
Traceback (most recent call last):
File "/tmp/awm_lesson8/mistake1_copy_attribute.py", line 3, in <module>
AttributeError: 'builtin_function_or_method' object has no attribute 'append'
Without parentheses, items.copy refers to the method itself instead of calling it. The variable named copy therefore does not contain a list. Use items.copy() to execute the method and receive the new outer list.
items = ["a", "b"]
copy = items.copy(1)
Traceback (most recent call last):
File "/tmp/awm_lesson8/mistake2_bad_copy_arg.py", line 2, in <module>
TypeError: list.copy() takes no arguments (1 given)
list.copy() copies the entire outer list and takes no index argument. If you want only a range, use the slicing syntax from Lesson 7.
a = ["x"]
b = a
b[1] = "y"
Traceback (most recent call last):
File "/tmp/awm_lesson8/mistake3_index_alias.py", line 3, in <module>
IndexError: list assignment index out of range
Aliasing changes the reference relationship; it does not change the indexing rules. The one-item list still has only index 0. Both a and b refer to that same one-item list, so index 1 remains out of range.
value = 42
backup = value.copy()
Traceback (most recent call last):
File "/tmp/awm_lesson8/mistake4_copy_on_int.py", line 2, in <module>
AttributeError: 'int' object has no attribute 'copy'
copy() here is a method provided by list objects. A plain integer does not expose that list method. Immutable values such as integers also do not need to be duplicated in the same way to protect them from in-place list-style mutation.
A mistake that raises no error: writing backup = original when you meant to preserve an independent list. Python accepts the assignment perfectly. The surprise appears later when one alias mutates the shared object. Not every bug comes with a traceback.
Supporting classroom explanations
This page is original A Wandering Mind instructional material. The two CPSC 1301K lectures below provide classroom reinforcement for list objects, references, aliasing, and cloning.
What it adds: a classroom explanation of the distinction between a list object and the variable references that point to it.
What it adds: a focused comparison of shared aliases and separate list copies, reinforcing why mutation can appear through more than one variable name.
Try it yourself
Predict whether each pair of variables points to one shared list or two outer lists before running the code. That prediction is more important than memorizing a particular copying spelling.
Exercise 1 · Spot the alias
Create original = ["cat", "dog"], assign alias = original, append "bird" through alias, then print both variables and the result of original is alias.
Show one answer
original = ["cat", "dog"]
alias = original
alias.append("bird")
print(original)
print(alias)
print(original is alias)
['cat', 'dog', 'bird']
['cat', 'dog', 'bird']
True
Exercise 2 · Make a separate outer list
Create original = [1, 2, 3], copy it with copy(), replace index 0 in the copy with 99, and print both lists.
Show one answer
original = [1, 2, 3]
copied = original.copy()
copied[0] = 99
print(original)
print(copied)
[1, 2, 3]
[99, 2, 3]
Exercise 3 · Predict a shallow-copy surprise
Create original = [["a"], ["b"]] and copied = original[:]. Append "x" to copied[0], then print both structures and test whether their first inner lists are the same object.
Show one answer
original = [["a"], ["b"]]
copied = original[:]
copied[0].append("x")
print(original)
print(copied)
print(original[0] is copied[0])
[['a', 'x'], ['b']]
[['a', 'x'], ['b']]
True
What you should carry into Lesson 9
You now know that assignment can create an alias rather than a copy. b = a makes both names refer to the same list, while a[:] and a.copy() create a new outer list. Those copies are shallow, so nested mutable elements may still be shared. Finally, is tests identity while == tests equality of value.
Next, you will expand your list toolbox. Continue to List Methods and Built-in Functions to work with common operations for adding, searching, ordering, counting, and summarizing list data.
