Lesson 16 / 25

Subtle Logic Bugs

Catch off-by-one errors, missing edge cases and shared state mistakes in generated code.

Right shape, wrong detail

Generated code often has the right structure but a wrong detail: an off-by-one error, a missing check for an empty list, a boundary that should be inclusive, a shared mutable default. These pass a casual glance. Run the code on boundary inputs (0, 1, empty, the maximum) and read loops and indexes slowly.

Two classic slips, run

I ran this. average([]) raises ZeroDivisionError. page_bad treats page 1 as the second page, so it skips the first three items; page_ok uses 1-based pages.

def average(nums):
    return sum(nums) / len(nums)        # crashes on []
def average_safe(nums):
    return sum(nums) / len(nums) if nums else 0.0

items = list(range(1, 11))
def page_bad(items, page, size):
    return items[page * size : page * size + size]     # 0-based by accident
def page_ok(items, page, size):
    start = (page - 1) * size
    return items[start : start + size]

print(average_safe([]), average_safe([2, 4]))
print(page_bad(items, 1, 3), page_ok(items, 1, 3))

Output:

0.0 3.0
[4, 5, 6] [1, 2, 3]

Try the zero, one and many cases

Most bugs hide at the boundaries. For any function, test with nothing, exactly one item, and many items before you trust it.

Quick check: Which input is most likely to expose a missing-check bug in `average(nums)`?

  • A list of 1000 numbers
  • A list of strings only
  • An empty list
  • A very long variable name
Answer

An empty list — Empty input is a classic boundary case that triggers division by zero.