Debugging Unintended Side Effects
Assignment creates a reference to a list, not a copy. Both variables point to the same object, so modifications through one affect the other.
When One Change Appears Twice
A list can change in a place you did not expect. One common cause is assigning one list variable to another and assuming that a second list has been created. In Python, assignment creates a reference to the existing list, not a copy. If two variables refer to the same list, modifying the list through either variable changes what the other variable sees.
Tracing Shared References
original = [1, 2, 3] alias = original alias[0] = 99
original
[99, 2, 3]
alias
[99, 2, 3]The important debugging question is not only “Which variable did I change?” Ask instead, “Which list object does this variable refer to?” With assignment, original and alias are two names for one object. A mutation made through alias changes the object itself, so original observes the same change.
Making an Independent List
When the original list must remain unchanged, create a separate list before modifying it. The source identifies two ways to make a shallow copy: slice notation [:] and the list() constructor. Both create a new list with the same elements. A later modification to the copied list does not affect the original list.
original
[10, 20, 30]
copy
[10, 200, 30]Replacing a Slice
Slice assignment uses the form t[start:end] = sequence. It replaces the elements selected by the slice with the elements in the replacement sequence. Because the replacement sequence can have a different number of elements, slice assignment can also change the list's length.
t = ['a', 'b', 'c', 'd', 'e', 'f'] t[1:3] = ['x', 'y']
What do you think happens?
If a two-element slice is replaced by a three-element sequence, what happens to the list's length?
Reveal answer
Answer: It increases by one.
The replacement sequence has one more element than the slice being replaced, so the list grows by one element.
Predicting Length Changes
To predict the result of slice assignment, compare two counts: the number of elements selected by the slice and the number of elements in the replacement sequence. Equal sizes preserve the list's length. A shorter replacement makes the list shrink. A longer replacement makes the list grow.
| Selected slice | Replacement sequence | Length effect |
|---|---|---|
| Two elements | Two elements | Length stays the same |
| Two elements | One element | List shrinks |
| Two elements | Three elements | List grows |
Compare the selected slice size with the replacement sequence size.
A copied list grows during slice assignment
Start with original = [10, 20, 30, 40, 50]. Create a separate copy, then replace the slice containing 30 and 40 with [200, 201, 202]. Predict both lists.
Create the copy: The copied list begins with the same five elements as original, but it is a separate list.
Identify the selected slice: The selected slice contains 30 and 40, so two elements will be replaced.
Compare sizes: The replacement sequence contains three elements, which is one more than the two selected elements.
Apply the replacement: The copied list grows from five elements to six elements. The original list is unchanged because the copy is separate.
original remains [10, 20, 30, 40, 50], while the copied list becomes [10, 20, 200, 201, 202, 50].
Debugging Checklist
Treating assignment as if it copied the list
Both variables refer to the same list object, so a modification through alias changes what original sees.
Fix:
Use original[:] or list(original) when an independent list is needed.Checking only the variable used for the mutation
Another variable may refer to the same list and may therefore show the same change.
Fix:
Trace every variable that refers to the list before deciding whether the mutation is unexpected.Assuming slice assignment preserves list length
The replacement sequence is longer than the selected slice, so the list grows.
Fix:
Count the selected elements and the replacement elements before applying the assignment.Copying when the program intentionally needs to update the original
A copy prevents the original list from changing, which may not match the program's intended logic.
Fix:
Choose copying or in-place modification explicitly and document the reason for an intentional in-place change.
- Find every assignment involving the list.
- Mark which variables refer to the same list and which were made with a copy.
- Locate each mutation, including element assignment and slice assignment.
- For slice assignment, identify the selected elements and count the replacement elements.
- Decide whether the observed change is an intended in-place update or an unintended shared-reference mutation.
Try the Trace
A program contains original = [1, 2, 3], alias = original, and alias[1:3] = [8]. Predict the value seen through original and decide whether the list grew, shrank, or stayed the same.
Hints
- Assignment makes alias refer to the same list as original.
- The slice [1:3] selects two elements.
- The replacement sequence contains one element.
Rewrite the previous situation so that modifying the second list does not change original. Use one of the two copying techniques described in this article.
Hints
- A slice with no start or end, such as original[:], creates a shallow copy.
- The list() constructor can also create a separate list with the same elements.
Key Takeaways
- Assignment between list variables creates another reference to the same list; it does not create a copy.
- Use [:] or the list() constructor when you need a separate list with the same elements.
- Slice assignment replaces multiple selected elements and can change list length.
- A shorter replacement shrinks the list, an equal-sized replacement preserves its length, and a longer replacement grows it.
- When debugging a surprising mutation, trace shared references, the selected slice, and the size of the replacement sequence.
Key Takeaways
- Assignment creates a shared reference rather than an independent list.
- Copy a list with [:] or list() before modifying it when the original must be preserved.
- Slice assignment updates a range of elements and may grow or shrink the list.
- Debug unintended side effects by tracing which variables share a list and counting slice elements.