Concepts / Debugging List Programs and Avoiding Common Pitfalls

Debugging List Programs and Avoiding Common Pitfalls

Aliasing means two variables point to the same list object, so changes through one variable affect the other.

  • Programming

The Unexpected Change

A list can appear to change in a part of a program that you did not expect. A common reason is aliasing: two variables point to the same list object. The variables have different names, but they still provide access to one shared list. Therefore, changing the list through one variable affects what the other variable shows.

What do you think happens?

Suppose two variables refer to the same list and the list is changed through one variable. What should you expect from the other variable?

  • It shows the change because both variables refer to the same list object

  • It keeps an independent copy of the old contents

  • It becomes unusable

Reveal answer
Answer:

It shows the change because both variables refer to the same list object

Aliasing means that two variables point to one shared list object. A change made through either variable affects that shared object.

Tracing Shared References

tvariable nameshared listone list objectchanged contentsvisible through both namesorigvariable namerefers torefers tois mutated
What happens to both variables when the shared list is mutated through only one of them?
Code walkthrough
t = [3, 1, 2]
orig = t
t.append(4)
print(t)
print(orig)
  1. 1
    The first line creates a list and associates it with t.
  2. 2
    The second line gives the same list object another variable name, orig.
  3. 3
    The third line mutates the list through t.
  4. 4
    The two print statements show the changed shared list through both names.
  • t starts as a list.
  • orig receives another reference to that same list object.
  • The mutation through t changes the shared list.
  • Both names display the changed contents.
Output
[3, 1, 2, 4]
[3, 1, 2, 4]

When debugging, trace every variable that may refer to a list. If a mutation made through one name becomes visible through another name, the names are acting as aliases for the same list object. This observation is different from merely seeing equal contents: the important question is whether a later change made through one variable is also visible through the other.

ashared caselistone objectaseparate caselist Aone objectbshared casebseparate caselist Banother objectrefers torefers torefers torefers to
How can you distinguish a shared list from separate lists with equal contents while tracing a mutation?

Slicing for Safety

When the original list must be preserved, create a defensive copy with orig = t[:]. The slice creates an independent list that can be modified safely. The new variable can then be changed without making it an alias for the original list.

toriginal listtoriginal contentsorigsame list before copyorigindependent contentsshared referenceseparate lists after t[:]
How does slicing create a separate list object, and what stays unchanged when the copy is mutated?
Code walkthrough
t = [3, 1, 2]
orig = t[:]
t.append(4)
print(t)
print(orig)
  1. 1
    The first line creates the list associated with t.
  2. 2
    The slice expression t[:] is used to create the defensive copy.
  3. 3
    Appending through t changes t's list.
  4. 4
    orig retains the contents of its independent list.
  • t starts as the original list.
  • orig = t[:] creates an independent list.
  • The mutation is made through t.
  • The original contents held by orig remain unchanged.
Output
[3, 1, 2, 4]
[3, 1, 2]
Before
orig = t

orig and t refer to the same list object.

After
orig = t[:]

orig is an independent list that can be modified safely.

The defensive-copy pattern is useful when one version of the data must remain available while another version is changed. The essential difference is not the spelling of the variable names; it is whether the names refer to one shared list or to separate lists.

Sorting Without Losing Data

sorted()new sorted listnew listoriginal remains unchangedsort()method on a listoriginal listcontents reorderedreturnsmodifies
What does each sorting operation return, and which original list changes?
ChoiceEffect on original listResult
sorted(t)Does not modify the originalA new sorted list
t.sort()Modifies the listThe list after sorting
copy = t[:]; copy.sort()Leaves t unchangedA sorted defensive copy

Use sorted() when you need a new sorted list, or copy the list before using sort() when the original must be preserved.

Code walkthrough
t = [3, 1, 2]
ordered = sorted(t)
print(t)
print(ordered)

t.sort()
print(t)
  1. 1
    The list t is created with its original order.
  2. 2
    Calling sorted(t) provides a separate sorted result stored in ordered.
  3. 3
    Printing t immediately afterward shows the original order remains.
  4. 4
    Calling the sort method on t changes the order of t.
  • sorted(t) produces a new sorted list.
  • The first print shows that t was not modified by sorted(t).
  • ordered contains the sorted result.
  • t.sort() changes t itself.
Output
[3, 1, 2]
[1, 2, 3]
[1, 2, 3]

Following Mutation Paths

listinitial contentsfirst namereferencemutationthrough first nameupdated listvisible through both namessecond namereferencerefers torefers toperformschangesobserves
How does a mutation travel through multiple references to the same list, and where does the program state change?
Worked example

Finding the Source of an Unexpected List Change

A program needs to keep an original list while changing another list. Determine why the original changes and select a safer assignment.

  1. 1
    Trace the assignment

    If the program uses orig = t, both names refer to the same list object. This creates aliasing.

  2. 2
    Trace the mutation

    When the list is changed through t, the shared list object changes. The changed contents are therefore visible through orig as well.

  3. 3
    Replace the shared reference

    Use orig = t[:] when an independent list is needed. The slice creates a defensive copy.

  4. 4
    Check sorting choices

    Use sorted(t) for a new sorted list without modifying t. If using the sort method, first make a defensive copy when t must remain unchanged.

Result

The unexpected change comes from aliasing. Use slicing for an independent list, and choose sorted() or a copied list with sort() according to whether the original must stay unchanged.

  • Common mistake

    Assuming two variable names automatically mean two independent lists.

    What it looks like
    orig = t
    Why it fails

    Aliasing means both names point to the same list object.

    Do this instead

    Use orig = t[:] when the second variable needs an independent list.

  • Common mistake

    Using the list sort method when the original order must be preserved.

    What it looks like
    t.sort()
    Why it fails

    The sort method modifies the list.

    Do this instead

    Use sorted(t), or create a defensive copy and call sort() on that copy.

  • Common mistake

    Expecting sorted(t) to reorder t itself.

    What it looks like
    sorted(t)
    Why it fails

    sorted() produces a new sorted list without modifying the original.

    Do this instead

    Store the returned list, such as ordered = sorted(t).

  • Common mistake

    Using sorted as a variable name.

    What it looks like
    sorted = [1, 2, 3]
    Why it fails

    The variable shadows the built-in function name.

    Do this instead

    Choose another variable name so sorted() remains available.

Debugging Practice

MEDIUM

A program must retain an original list and also produce a sorted version. Write a short plan that names the safest operation for each step: creating the independent version, sorting without changing the original, and sorting a copy with the list method.

Hints
  • Use slicing when you need a defensive copy.
  • Use sorted() when you want a new sorted list without modifying the original.
  • Use the sort method on the copy if you first preserve the original with slicing.
  1. 1
    Aliasing is the central debugging hazard in this topic: two variable names can point to one shared list object, so a mutation through one name appears through the other. Follow references when tracing an unexpected change. Use orig = t[:] to make an independent list. Use sorted() for a new sorted list that leaves the original unchanged, or copy first and then use sort(). Keep sorted available by never using it as a variable name.

Key Takeaways

  • Aliasing occurs when two variables point to the same list object.
  • A mutation through one alias affects what the other alias shows.
  • Use orig = t[:] to create a defensive copy that can be modified independently.
  • Use sorted() for a new sorted list, and use sort() when you intend to modify a list.
  • Do not use sorted as a variable name because it shadows the built-in function.

Related concepts

More concepts starting with D