Guide Python Intermediate

10.16 Common Mistakes

The most common list bugs in Python -- off-by-one IndexError, silently skipping elements by mutating a list while iterating over it, and the shared-reference and mutable-default-argument traps.

2 min read

Index Errors

Accessing an index that doesn’t exist — especially easy off-by-one mistakes near a list’s boundaries.

>>> l = [1, 2, 3]
>>> l[5]
Traceback (most recent call last):
IndexError: list index out of range

Modifying a List While Iterating Over It

Removing items from a list while iterating over it directly causes elements to be silently skipped, because the indices shift underneath the iterator as items are removed.

# WRONG: mutating the list you're iterating over
l = [1, 2, 3, 4, 5]
for x in l:
    if x % 2 == 0:
        l.remove(x)     # shifts remaining elements -- some get skipped!

# CORRECT: iterate over a COPY, mutate the original
l = [1, 2, 3, 4, 5]
for x in l[:]:
    if x % 2 == 0:
        l.remove(x)

>>> l
[1, 3, 5]

Shared References

Forgetting that b = a shares the same list object rather than copying it — see 10.11 Copying and Mutability for the full explanation and the shallow-vs-deep distinction.

Mutable Default Arguments

Using a list as a function’s default argument value — the default is created once at definition time and shared across every call that doesn’t override it. Fully explained, with the fix, in 10.11 Copying and Mutability and 5.15 Common Mistakes.

Quick Interview Answer

“Four mistakes account for most list bugs: off-by-one IndexErrors near a list’s boundary; mutating a list while iterating over it directly, which silently skips elements because removal shifts every later index backward — the fix is iterating over a copy (for x in l[:]) while mutating the original; forgetting b = a shares one list rather than copying it, so a mutation through b is visible through a too; and the mutable-default-argument trap, where a list default is created once at function-definition time and silently accumulates across calls unless None is used as the sentinel instead.”

Common Mistakes

  • Removing items from a list with l.remove(x) or del l[i] inside a for x in l: loop over that same list — build a new filtered list, use a comprehension, or iterate over l[:] instead.
  • Assuming two separately-created lists that happen to look identical ([1, 2] == [1, 2]) are the same object — == compares content, is compares identity; see 10.6 List Operators.
  • Defining def f(items=[]) and being surprised the default list accumulates values across unrelated calls — default to None and create the list inside the function body.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form