Following the Counter by Hand
Tracing a loop line by line in a table reveals how the counter grows and where an off-by-one boundary error slips in. · 11 min
A loop hides its work. It says repeat this, and the machine runs the body many times while you see only the final result. To predict what a loop does — or to find why it does the wrong thing — you slow it down and record every pass by hand. That record is called a trace (a row-by-row account of the program as it runs), and building one is the most reliable debugging skill in this whole course.
Guess before you learn
How many lines does this loop print?
``
i = 1
while i <= 3:
print(i)
i = i + 1
``
It prints three lines: 1, 2, 3. The counter starts at 1, and the test i <= 3 lets it through for 1, 2, and 3, then blocks 4. If you guessed 2 or 4, keep that pencil mark — the two ends of a loop are exactly where miscounts happen, and this whole folio is about pinning them down.
9–12
3–5
A loop counter is a number that ticks up each time around. To follow it, count on your fingers: the start value, then one more each pass, until the loop's rule says stop. The number you end on is the answer — and it is easy to be off by exactly one, either too soon or too far.
6–8
To trace a loop, make a small table. One column for the counter, one for the loop's test (True or False), one for anything printed. Fill one row per pass: write the counter's value, check the test, record the output, then update the counter. When the test comes out False, the loop ends. The boundary — the exact value where the test flips — is where an off-by-one error (a miscount of one at the edge) almost always hides.
9–12
Tracing turns an invisible process into a table you can audit. Each row is the program's state at one moment: the counter, the condition's truth value, the output. Read the rows until the condition is False. Two choices set a loop's length: the counter's start value and the comparison in the test. Change <= to <, or start at 0 instead of 1, and the number of passes shifts by one. That single-step shift at the boundary is the off-by-one error, and a trace table exposes it at once.
K–2
Count the stairs out loud. Step one. Step two. Step three. Your foot lands once on each stair. A loop is the same: it does its job once for each step, and you can point at every single one.
If you count one stair too many, you trip. Loops trip the same way. Going by hand, one step at a time, is how you catch the extra stair before it catches you.
Undergrad
The discipline generalizes to a loop invariant: a statement about the counter that holds before and after every pass. For i counting 1 to n under the test i <= n, the invariant is that on entry to the body, i names the pass number and 1 <= i <= n. Termination follows because i strictly increases toward a fixed bound. The classic fencepost error — counting posts versus the gaps between them — is the off-by-one you get when that boundary is set one place too high or too low.
Postgrad
Formally, the correctness of a while loop rests on three obligations: the invariant holds initially, the body preserves it, and on exit the invariant together with the negated guard implies the postcondition. Termination needs a variant — a nonnegative integer that strictly decreases each pass, here n - i. Off-by-one bugs are boundary defects in exactly one of these obligations, most often the guard. Python's range(n) is half-open over [0, n) precisely so that composed loops share fencepost conventions and stop misaligning at their seams.
trace
A row-by-row record of a program's state as it runs: one row per step, with columns for each variable that changes and for any output.
Why is this true?
Why does changing the test from i <= n to i < n change how many times the loop runs?
Because the test decides what happens at the boundary. With i <= n, the value n still passes and the body runs for it; with i < n, the value n is refused and the loop runs one fewer time. The endpoints are where the count is won or lost.
Trace the counter: while i <= 4 — the steps fade as you master them
i = 1 + 1
i = 2 + 1
i = 3 + 1
i = 4 + 1
printed: 1 2 3 4
Now watch the counter itself. If you plot the value it holds on each pass, you get a staircase: one even step per pass, ending on the last value the test allows. Sketching that staircase before you run the code is how you feel an off-by-one coming — the line either stops one step short or reaches one step too far.
That is the whole method: a trace table makes a loop's every step visible, and the counter's staircase shows exactly where it starts and stops. Next folio begins Unit IV — you will name a block of steps once and run it whenever you call it, which is the first real tool for keeping large programs traceable.
Practice — new ink and old, interleaved
1.Review from folio 1: in what order does the computer run a program's statements?
From the top line to the bottom line, one statement at a time, exactly as written.
How close were you? Grade yourself honestly — it sets your review date.
2.What is the last value i takes in for i in range(2, 6):?
3.Does this loop ever stop?
``
n = 3
while n > 0:
print(n)
``
4.How many times does the body run?
``
for i in range(4):
print('hi')
``
5.Order what one row of a trace table records, in the sequence you fill it in.
- the counter's value at the start of the pass
- the test's truth value
- the output produced
- the counter's value after updating
6.(2 < 3) and (3 < 2) evaluates to?
7.What does 3 < 5 and 5 < 4 evaluate to?
8.not (5 == 5) evaluates to?
9.Evaluate 7 + 3 * 2 under Python's precedence rules.
10.How many numbers does range(0, 20, 5) produce?