Algorithmic Thinking & Problem Solving~20 min

Iterative Improvement

Get a simple, obviously correct solution working first and verify it — then improve it in small steps, re-verifying after each one.

By the end of this lesson, you can

  • Explain iterative improvement as getting a correct, simple solution working first, then improving it in small, re-verified steps
  • Apply the improve-one-thing-at-a-time discipline to a working solution
  • Explain why changing several things at once makes a new bug harder to isolate

Why it matters

Every lesson in this module has led here. Reaching straight for the cleverest-looking solution combines two hard things at once — correctness and complexity — so a bug could be coming from either. Iterative improvement separates them: get something simple working and verified first, then improve it in small steps, checking correctness again after each one.

Mental model

This is the last step of the loop introduced back in problem decomposition — Understand, Model, Plan, Implement, Verify, Improve — and it has its own discipline:

  1. Get a simple, obviously correct version working.
  2. Verify it, ideally with the kind of assert checks from the debugging and testing lesson.
  3. Improve one thing.
  4. Re-run the same checks. If they still pass, the improvement was safe; if not, there’s exactly one change to suspect.
  5. Repeat.

Small example

Start with has_duplicates_slow from the previous lesson — simple, easy to trust is correct — and verify it:

PythonStep 1: correct and verified, even though it's quadratic
def has_duplicates_slow(numbers):
    for i in range(len(numbers)):
        for j in range(len(numbers)):
            if i != j and numbers[i] == numbers[j]:
                return True
    return False

assert has_duplicates_slow([1, 2, 3]) == False
assert has_duplicates_slow([1, 2, 2]) == True
assert has_duplicates_slow([]) == False

All three assertions pass — including the empty-list edge case. Now improve it, and check the exact same assertions still hold:

PythonStep 2: one improvement, then re-verified with the same checks
def has_duplicates_fast(numbers):
    seen = set()
    for number in numbers:
        if number in seen:
            return True
        seen.add(number)
    return False

assert has_duplicates_fast([1, 2, 3]) == False
assert has_duplicates_fast([1, 2, 2]) == True
assert has_duplicates_fast([]) == False

All three still pass. Because only one thing changed, and the same checks confirmed the change didn’t break anything, this improvement can be trusted.

Trace it

One change at a time, re-verified after each
StepWhat changedSame three assertions still pass?
1(nothing yet — first working version)Yes
2Switched to a set for membership testingYes — safe to keep

Check your understanding

After improving a working, verified function, what should happen next?
Or reveal the answer without checking

Answer:Re-run the same checks that verified the original version, before making any further changes
Re-running the same checks after each change is what confirms the improvement didn't break anything — and if it did, exactly one change is left to suspect.

Why does changing several things at once make a new bug harder to isolate?
Or reveal the answer without checking

Answer:If a check fails afterward, any of the several changes could be the cause, and there's no way to tell which without undoing them individually anyway
A failing check after one change points at exactly one suspect. After several simultaneous changes, the same failure could be caused by any of them, and isolating the real cause means separating them out anyway.

Practice: warm-up

Starting from this verified, working function, apply one improvement — replacing the manual loop with Python’s built-in sum() — and confirm the same assertion still passes.

def total(numbers):
    result = 0
    for number in numbers:
        result = result + number
    return result

assert total([1, 2, 3]) == 6

Stuck? Reveal one hint at a time.

  1. Hint 1

    sum(numbers) does exactly what the loop in total does — add up every number in the list.

  2. Hint 2

    After rewriting, run the exact same assertion again rather than assuming it still holds.

Reveal the improved version

Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.

def total(numbers):
    return sum(numbers)

assert total([1, 2, 3]) == 6   # still passes — the improvement was safe

Practice: apply it

A teammate “improved” a verified function by changing three things at once — adding an empty-list check, introducing a new count variable, and rounding the result — and now an existing regression test fails. (round(value, 2) rounds to 2 decimal places — new syntax, but readable from its name.)

def average(numbers):
    if not numbers:
        return 0.0
    total = sum(numbers)
    count = len(numbers)
    return round(total / count, 2)

assert average([1, 2, 4]) == 2.3333333333333335

The original, verified version only computed sum(numbers) / len(numbers), with no rounding and no empty-list check — and that exact, unrounded value is what the existing test expected.

Why is it hard to tell which of the three changes broke this test, just from the failure?
Or reveal the answer without checking

Answer:Three separate changes were made at once, so the failure alone doesn't say whether rounding, the empty-list branch, or the new count variable is responsible
The rounding is the actual cause here — but with three simultaneous changes, that's not obvious from the failure alone. Any of the three could plausibly explain a changed return value without re-checking each individually.

Modification challenge: starting from the original verified version, reapply the three changes one at a time, re-running assert average([1, 2, 4]) == 2.3333333333333335 after each, to confirm the rounding step is exactly the one that breaks it.

Summary

  • Get a simple, obviously correct solution working and verified first — before making it faster, shorter, or more elegant.
  • Improve one thing at a time, and re-run the same checks after each change.
  • A failing check after a single change points at exactly one suspect; after several at once, it doesn’t.
  • This closes the loop this module opened with: Understand, Model, Plan, Implement, Verify, Improve.