Trace table examples: how to trace a loop by hand
Published
By the Unrust team · 8 min read · Editorial method
Worked trace table examples in Python: a for loop over range, a while loop, a running maximum and a nested loop, plus column rules and common mistakes.
Open the free companion tool →What a trace table is and which columns it needs
A trace table is a grid you fill in while running a program by hand. Each column holds a variable, a condition or the output, and each row records what changed at one step or one loop iteration. When the table is complete, its last row predicts the final state and its output column predicts exactly what the program prints.
Choose the columns before you start. Give every variable that is assigned inside the loop its own column, add a column for each condition the program tests, and finish with an output column. Values that never change after setup, such as a fixed input list, can be written once above the table instead.
Decide the row rule as well. For short straight-line code, one row per executed line works. For loops, one row per iteration is easier to read, with a Start row for the values before the loop begins. Some tables fill a cell only when its value changes; others repeat the current value on every row. Either works if you stay consistent. The examples below repeat values so each row is readable on its own.
Column What to write When to fill it
--------- --------------------------------- -------------------------------
Row Start, an iteration number or End Every row
Variable Its value after this step runs Whenever the variable is assigned
Condition True or False Each time the test is evaluated
Output Exactly what print() writes Only on rows that printExample 1: a for loop over range(1, 8)
In Python, range(1, 8) produces 1, 2, 3, 4, 5, 6 and 7. The stop value 8 is never part of the sequence, so this loop has exactly seven iteration rows. The if test lets only even values through, so total ends at 2 + 4 + 6 = 12 and the program prints 12.
Fill the condition column before touching total. On row 2, i % 2 == 0 is True, so total becomes 0 + 2. On row 3 the test is False and total stays 2. Writing the test result first stops you from updating a variable on a row where the branch never runs.
A for loop over a range has no visible counter check, but it still ends: once the range has no values left, execution moves to the print statement. Record that as an End row so the output appears after the loop rather than inside it.
total = 0
for i in range(1, 8):
if i % 2 == 0:
total = total + i
print(total)
Row i i % 2 == 0 total output
----- -- ----------- ------ ------
Start - - 0
1 1 False 0
2 2 True 2
3 3 False 2
4 4 True 6
5 5 False 6
6 6 True 12
7 7 False 12
End - - 12 12Example 2: a while loop and its final failed check
A while loop checks its condition one more time than its body runs, and that last check is the row most often left out of a trace table. Here, n > 1 is evaluated seven times: True six times, so the body runs six times, then False when n reaches 1 and the loop exits.
The body has two branches, so add a column for n % 2 == 0 and evaluate it using the value of n at the start of the iteration, before n is reassigned. Then write the new n and the new steps. The sequence 10, 5, 16, 8, 4, 2, 1 shows why the table helps: the odd branch makes n jump upward, which is easy to lose track of in your head.
n = 10
steps = 0
while n > 1:
if n % 2 == 0:
n = n // 2
else:
n = 3 * n + 1
steps = steps + 1
print(steps)
Row n > 1 n % 2 == 0 n steps output
----- ------ ----------- --- ------ ------
Start - - 10 0
1 True True 5 1
2 True False 16 2
3 True True 8 3
4 True True 4 4
5 True True 2 5
6 True True 1 6
Exit False - 1 6 6Example 3: a running maximum over a list
When a loop indexes into a list, give the index and the element their own columns. This program finds the largest score and the position where it first appears, and it prints 9 1. The second 9, at index 3, does not replace the first because 9 > 9 is False. The strict comparison is what keeps the first position.
This example also shows why a trace table needs deliberate test data. Starting best at 0 works for these scores, but if every score were negative, nothing would pass the test and the program would print 0 -1: a maximum that is not in the list. Starting best at scores[0], and handling an empty list separately, fixes that. A table only checks the inputs you trace, so choose inputs that probe the edges.
scores = [4, 9, 2, 9, 7]
best = 0
position = -1
for index in range(len(scores)):
if scores[index] > best:
best = scores[index]
position = index
print(best, position)
Row index scores[index] > best? best position output
----- ------ -------------- -------- ----- --------- ------
Start - - - 0 -1
1 0 4 True 4 0
2 1 9 True 9 1
3 2 2 False 9 1
4 3 9 False 9 1
5 4 7 False 9 1
End - - - 9 1 9 1Example 4: a nested loop, one row per inner iteration
For nested loops, write one row per inner-loop iteration and keep the outer variable on every row. The inner loop starts again from zero each time the outer loop advances. Here size takes 1, 2 and 3, the inner loop runs once, twice and three times, and the program prints *, ** and *** on three separate lines.
Give the reset line = "" its own row at the start of each outer iteration. If you skip that reset in your trace, or if the code initializes line once above the outer loop instead, the stars accumulate and the output becomes *, *** and ******. Tracing the reset explicitly is how you catch that bug before running anything.
for size in range(1, 4):
line = ""
for j in range(size):
line = line + "*"
print(line)
Row size j line output
--- ---- -- ------ ------
1 1 - ""
2 1 0 "*"
3 1 - "*" *
4 2 - ""
5 2 0 "*"
6 2 1 "**"
7 2 - "**" **
8 3 - ""
9 3 0 "*"
10 3 1 "**"
11 3 2 "***"
12 3 - "***" ***Try it: how many times is the condition checked?
Trace this loop before revealing the answer. Draw columns for i, i < len(values), values[i] > 4, count and output. Then answer two questions: what does the program print, and how many times is the condition i < len(values) evaluated?
values = [5, 3, 8]
count = 0
i = 0
while i < len(values):
if values[i] > 4:
count = count + 1
i = i + 1
print(count, i)Reveal the answer and explanation
The program prints 2 3. The values 5 and 8 pass the test, so count reaches 2. After the third iteration i becomes 3, which is why the second printed number is 3, not 2.
The condition is evaluated four times: it is True when i is 0, 1 and 2, and False when i is 3. If your table has only three condition rows, it is missing the failed check that ends the loop. That exit row is also where i equals the length of the list, which is why reading values[i] after this loop would raise an IndexError.
Common trace table mistakes and how to catch them
Most wrong trace tables fail in the same few places: the final loop check is missing, a value is written before the line that assigns it, range is treated as including its stop value, a reset is skipped, output appears on a row that prints nothing, or two names for one list are traced as separate copies.
When your table disagrees with the real output, do not redo the whole trace. Find the first row where your values differ from the program's actual state. That row points to the statement you misread, which teaches you more than the corrected final answer does.
1. Missing the final check
Symptom: a while loop has N rows instead of N + 1
Fix: add an exit row where the condition is False
2. Writing a value too early
Symptom: total changes on a row where the test is False
Fix: fill the condition column before the variables
3. Treating range(a, b) as inclusive
Symptom: an extra row where i equals b
Fix: with step 1, the last value is b - 1
4. Skipping a reset inside an outer loop
Symptom: strings or counters keep growing across rows
Fix: give the reset its own row
5. Inventing output
Symptom: output on rows with no print() call
Fix: only print() lines fill the output column
6. Treating an alias as a copy
Symptom: two names for one list hold different values
Fix: check whether both names refer to one objectTrace table, debugger or visualizer: which to use
Use a trace table to make a prediction and a debugger or visualizer to check it. A trace table records what you think the program does; Python's pdb debugger, or a step-through tool such as Python Tutor, shows what it actually does. The useful part is comparing the two row by row and explaining the first difference.
Python Tutor lets you write a short program in the browser and step through it, drawing variables, objects and stack frames at each step; despite the name, it also handles Java, C, C++ and JavaScript. pdb sets breakpoints and single-steps at the source line level from a terminal. Both show state well, but neither tells you which assumption in your head was wrong. Fill in your table first, then step through until the first disagreement.
Approach Shows Best for
--------------- ---------------------------- -------------------------------------
Trace table Your predicted state Exams, interviews, code review
Visualizer Actual state, drawn per step Checking a short program line by line
Debugger (pdb) Actual state, on request Real projects, breakpoints, large inputs
print() logging Actual values you choose Quick checks across many iterationsTrace table questions
Are trace tables and arrays the same? No. An array or list is a data structure inside the program. A trace table is a record you write outside the program to follow its execution. When a program uses a list, its contents, or the element at the current index, become one or more columns in the table.
How many rows does a trace table need? One per step you choose to record. For a loop traced per iteration, that is the number of iterations plus a Start row and, for a while loop, an exit row for the failed check. If the count surprises you, recheck the loop bounds before anything else.
Can you trace a function call or recursion in a table? Yes. Add a column for the call and its arguments, and a row each time a call starts and returns. For deep recursion, a separate list of call frames is easier to read than one very wide table.
Do I still need trace tables if I can run the code? Running code tells you what happened; tracing tells you whether you understood why. That matters in exams, technical interviews and code review, where you often cannot execute the code, and when you review a plausible change that passes ordinary inputs.
Practise tracing without the table
Trace tables are scaffolding. With practice you stop writing every row and track only the values that decide the outcome: the loop boundary, the value a branch tests, and anything that is reset or shared between names. The Unrust code-tracing guide applies the same state-table approach to a continue statement and an array alias.
For JavaScript for loops, the free loop boundary explorer takes a whole-number start, boundary, comparison and step, then shows the values the body visits and the first failed check. That final check is the same boundary reasoning as the exit row in Example 2.
To see how your tracing holds up without notes, Unrust's free diagnostic samples six dimensions with AI and hints closed: recall, tracing, comprehension, debugging, idiom and AI-slop detection. You can take it in JavaScript, TypeScript or Python. Treat the result as a snapshot of one sitting and use it to choose what to practise next.
References and next steps
Check your tracing without a table
Take Unrust's free diagnostic with AI and hints closed. Tracing is one of six dimensions it samples, alongside recall, comprehension, debugging, idiom and AI-slop detection.