Debugging Unintended Side Effects
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.
Try it: Debug by Tracing Execution
How to debug a program that runs but gives the wrong answer: compare expected and actual output, trace the lines in the order they really run with a variable watch table (or an inserted debug print), find the first line where a value goes wrong, then choose a fix and re-run to check it.
How it works
- State the expected output from the specification, run the program and compare: a difference is the evidence you debug from.
- Trace in execution order, not file order: loops repeat lines, if/elif picks one branch, and the watch table records every variable before each line runs.
- An inserted print('debug: ...', value) shows the same state in the output; it must not change what the program itself prints.
- Look for the first place a value differs from what it should be (an off-by-one range, > instead of >=, a total reset inside the loop, two names for one list, a str from input()). Where the wrong value shows up is only the symptom.
- Change one thing with a reason, re-run, and compare again: a fix that makes an error disappear can still leave the output wrong.
Default run (14 steps): Sum 1..n: Add up the whole numbers from 1 to n (n included) and print the sum. Press Play to trace it line by line. … Finished, but the output is wrong: got sum of 1 to 5 is 10, expected sum of 1 to 5 is 15.
Simplified: The lab's own small Python interpreter runs fixed, hand-written programs (you pick the program, the fix and a value; your text is never executed). Every program the lab can show was run in CPython 3.12 and the lab reproduces its output, its traceback text and its line-by-line trace. Six short programs, one editable value each; input() receives the value you type in the lab. Lines starting with "debug:" are ignored when comparing the output with the expected output.
Loading the simulation…
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- 1After the first line, original refers to a list containing 1, 2, and 3.
- 2After the second line, alias refers to that same list. No separate list has been created.
- 3The third line changes the first element of the shared list to 99.
- 4Because both names refer to the shared list, original now also sees [99, 2, 3].
- The list [1, 2, 3] is created.
- Assignment makes alias another name for the same list.
- Changing the first element through alias changes the shared list.
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]original = [10, 20, 30]
copy = original[:]original and copy contain the same elements, but they are separate lists
copy[1] = 200copy is [10, 200, 30]; original remains [10, 20, 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']- 1Before the assignment, t contains six elements.
- 2The slice t[1:3] refers to ['b', 'c'].
- 3The two selected elements are replaced by ['x', 'y'].
- 4The replacement has the same size as the selected slice, so t remains six elements long.
- The slice t[1:3] selects the elements at positions 1 and 2.
- Those elements are b and c.
- The replacement sequence contains x and 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
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.
- 1Create the copy
The copied list begins with the same five elements as original, but it is a separate list.
- 2Identify the selected slice
The selected slice contains 30 and 40, so two elements will be replaced.
- 3Compare sizes
The replacement sequence contains three elements, which is one more than the two selected elements.
- 4Apply 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
Common mistake Treating assignment as if it copied the list
What it looks likealias = originalWhy it failsBoth variables refer to the same list object, so a modification through alias changes what original sees.
Do this insteadUse original[:] or list(original) when an independent list is needed.
Common mistake Checking only the variable used for the mutation
What it looks likealias[0] = 99Why it failsAnother variable may refer to the same list and may therefore show the same change.
Do this insteadTrace every variable that refers to the list before deciding whether the mutation is unexpected.
Common mistake Assuming slice assignment preserves list length
What it looks liket[1:3] = [200, 201, 202]Why it failsThe replacement sequence is longer than the selected slice, so the list grows.
Do this insteadCount the selected elements and the replacement elements before applying the assignment.
Common mistake Copying when the program intentionally needs to update the original
Why it failsA copy prevents the original list from changing, which may not match the program's intended logic.
Do this insteadChoose copying or in-place modification explicitly and document the reason for an intentional in-place change.
- 1Find every assignment involving the list.
- 2Mark which variables refer to the same list and which were made with a copy.
- 3Locate each mutation, including element assignment and slice assignment.
- 4For slice assignment, identify the selected elements and count the replacement elements.
- 5Decide 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
- 1Assignment between list variables creates another reference to the same list; it does not create a copy.
- 2Use [:] or the
list()constructor when you need a separate list with the same elements. - 3Slice assignment replaces multiple selected elements and can change list length.
- 4A shorter replacement shrinks the list, an equal-sized replacement preserves its length, and a longer replacement grows it.
- 5When debugging a surprising mutation, trace shared references, the selected slice, and the size of the replacement sequence.
At a glance
- 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.