Tracing code and trace tables

Big Idea

A trace table is a tool for reading code without losing track of what is happening. You make a column for each variable and a row for each line. As you work through the program, you fill in what changes. By the end, you know exactly what every variable contained at every moment — and you can predict the output with confidence.

This article is part of the Code Reading, Tracing & Prediction section. If you have not read How to Read Code yet, start there. Trace tables are the main technique used throughout this section, so understanding them well pays off across every other article.

LevelWhat it coversWhen to read it
Basic ideaWhat a trace table is and how to fill one inRight now.
At a deeper levelHow to handle conditionals and output in a trace table; common mistakesOnce you have filled in your first table on your own.
At the deepest levelWhat trace tables reveal about memory and state; when they become unwieldyWhen you want to understand what the table is really modelling.

1   What a Trace Table Is

Basic Idea

When you trace a program in your head, you have to remember what every variable contains while also reading forward. That is too much to hold in memory at once. A trace table solves this by moving the remembering onto paper.

The structure is simple:

  • One column for the line of code being executed
  • One column for each variable in the program
  • One column for output — what gets printed
  • One row for each line that does something

You fill in cells as you go. When a variable changes, you write its new value. When a variable does not change on a given line, you leave that cell blank (or copy the previous value — either convention works, as long as you are consistent).

At a Deeper Level

Here is what a trace table looks like in practice. Take this program:

x = 5
y = 2
x = x + y
y = x - 1
print(y)

Set up the table with a column for the line, a column for each variable (x and y), and an output column. Then work through the program one line at a time.

LinexyOutput
x = 55  
y = 2 2 
x = x + y7  
y = x - 1 6 
print(y)  6

Reading the Output column top to bottom gives you the program's complete output: 6.

Notice the third row: x = x + y reads the current value of x (5) and the current value of y (2), adds them, and stores the result back in x. The table makes this visible — you look left at the previous rows to find the current values, then write the new value in the current row.

How to read a trace table

To find what a variable contains at any point in the program, look at that variable's column and scan upward from the current row until you find the most recent non-empty cell. That is the current value. This is exactly what the computer does — it always uses the most recently stored value.

At the Deepest Level

A trace table is a hand-made record of a program's state at each step. In computer science, state means the complete set of values stored in memory at a given moment. Each row in the trace table is a snapshot of state after one line executes.

This is exactly what a debugger does automatically. When you pause execution in VS Code's debugger and look at the Variables panel, you are reading the program's current state — the same information your trace table records manually. Learning to build trace tables by hand trains you to think in terms of state, which makes debuggers far easier to use when you reach them.

2   How to Set Up a Trace Table

Basic Idea

Before you start tracing, scan the whole program and identify every variable. Make one column per variable. If you miss a variable and need to add it later, that is fine — but scanning first saves time.

The steps:

  1. Read the whole program once without tracing.
  2. List every variable name you see.
  3. Draw the table: Line | variable 1 | variable 2 | ... | Output
  4. Start at line 1 and work downward, one row per executable line.
  5. Only write in a cell when that variable's value changes on that line, or when there is output.

Comments (# like this) and blank lines do not change any values, so they do not get rows.

At a Deeper Level

Two conventions exist for cells where a variable does not change:

ConventionWhat you write in an unchanged cellPros and cons
Leave blank(nothing)Faster to fill in. Changes stand out clearly. Requires scanning upward to find the current value.
Carry forwardRepeat the previous value in every rowThe current value is always visible in the same row. Takes longer to fill in and can feel repetitive.

In this course, either convention is acceptable. The leave-blank approach is faster and is used in the examples throughout this article. Use whichever helps you stay accurate.

One row per line, not one row per variable

A common mistake is to create one row for each variable rather than one row for each line of code. This breaks the table — you lose track of the order in which things happened. Rows represent time (the order of execution). Columns represent space (the variables in memory). Keep those two ideas separate.

At the Deepest Level

The choice between "leave blank" and "carry forward" reflects two different ways of modelling memory. The carry-forward approach models memory as something that always has a value — which is accurate. Every variable always contains something after it is first assigned. The leave-blank approach highlights only the moments of change — which is more useful for spotting bugs, because bugs are usually caused by a value changing at the wrong time or not changing when it should.

Experienced programmers reading someone else's code often do a mental version of the leave-blank approach: they track only the lines that matter, skipping over lines that simply use a value without modifying it.

3   Handling Conditionals in a Trace Table

Basic Idea

When your program has an if statement, you need to decide which branch runs — and only trace the lines inside that branch. The lines in the branch that does not run are skipped entirely. They do not get rows in the table.

To decide which branch runs, look at the condition and check it against the current values in your table.

At a Deeper Level

Here is a program with a conditional:

score = 72
grade = "F"
if score >= 60:
    grade = "Pass"
else:
    grade = "Fail"
print(grade)
LinescoregradeOutput
score = 7272  
grade = "F" "F" 
if score >= 60:Condition: 72 >= 60 → True. Take the if branch.
    grade = "Pass" "Pass" 
print(grade)  Pass

The condition row does not change any variable. It makes a decision. Write out the condition, evaluate it using the current values, and note which branch you are taking. Then skip all the rows inside the branch you are not taking.

In this example, because score >= 60 is True, the line grade = "Fail" inside the else block never runs. It does not appear in the table at all.

Write out the condition evaluation

Do not evaluate the condition silently in your head and skip straight to the branch. Write it out in the table — even if just as a note in a merged cell. This forces you to use the actual current values rather than the values you expect them to be. Most conditional tracing errors happen because the programmer used an assumed value rather than looking up the current one.

At the Deepest Level

When a condition is evaluated, Python constructs a temporary Boolean value — True or False — that is not stored in any named variable. The condition row in your trace table captures this temporary evaluation. In a real interpreter, this temporary value exists in the CPU for the duration of the branch decision and is then discarded.

Complex conditions using and, or, and not are evaluated according to short-circuit rules: Python stops evaluating as soon as the result is certain. For and, if the left side is False, the right side is never checked. For or, if the left side is True, the right side is never checked. When tracing complex conditions, evaluate left to right and note where evaluation stops.

4   Handling Loops in a Trace Table

Basic Idea

Loops repeat lines of code. In a trace table, each time through the loop gets its own set of rows — even though it is the same lines of code repeating. A loop that runs three times will produce three groups of rows in your table.

This is the part where trace tables get longer. That length is the point: it forces you to see exactly what is happening on each repetition, rather than glossing over it.

At a Deeper Level

Here is a short loop:

total = 0
for i in range(3):
    total = total + i
print(total)
LinetotaliOutput
total = 00  
for i in range(3): — iteration 1 0 
total = total + i0  
for i in range(3): — iteration 2 1 
total = total + i1  
for i in range(3): — iteration 3 2 
total = total + i3  
print(total)  3

A few things to notice:

  • range(3) produces the values 0, 1, 2 — not 1, 2, 3. The loop runs three times, and i starts at 0.
  • On iteration 1, total = total + i is 0 + 0 = 0. The value does not change, but it is still worth writing in the table to confirm your reasoning.
  • The final value of total is 3, which is 0 + 1 + 2.
Label your iterations

When the loop header appears multiple times in your table, add a label — "iteration 1", "iteration 2" — to keep rows from blurring together. This is especially important with while loops, where the condition is re-evaluated each time and you need to check it carefully on every iteration.

At the Deepest Level

The trace table for a loop grows linearly with the number of iterations. For a loop that runs 100 times, a full trace table would have hundreds of rows — impractical to write by hand. In practice, you trace enough iterations to confirm your understanding of the pattern (usually two or three), then reason about the remaining iterations abstractly.

This is the same approach used in mathematical induction: show that the base case is correct, show that one step follows correctly from the previous, then conclude that all subsequent steps work the same way. For a loop that accumulates a total, tracing three iterations and seeing the pattern is sufficient to predict what the 100th iteration would produce — provided the loop body does not contain conditional logic that behaves differently in edge cases.

5   Common Mistakes

Basic Idea

These are the mistakes that appear most often when students fill in trace tables for the first time. Knowing what they are before you make them saves time.

MistakeWhat it looks likeHow to avoid it
Using an old valueYou calculate x + y using the value of y from two rows ago instead of the most recent oneAlways scan upward in the column to find the most recent non-empty cell before using a variable's value
Writing the expression instead of the resultYou write x + y in the cell instead of the computed value, such as 7Always evaluate the expression and write the final value. The table records what the computer stores — not the calculation it performed to get there.
Tracing skipped branchesYou include rows for code inside an else block even when the if condition was TrueWhen a condition is True, the else block does not run. Draw a line through those rows or leave them out entirely.
Forgetting that range(n) starts at 0You set i = 1 on the first iteration of for i in range(3)range(3) produces 0, 1, 2. Always write out the sequence before tracing the loop.
Stopping the loop too early or too lateYou give a loop three iterations when it should have four, or vice versaEvaluate the loop condition before each iteration, including the one that finally fails. The loop stops when the condition becomes False — write that out explicitly.
At a Deeper Level

The most damaging mistake is not any of the ones above — it is not checking your trace against the actual output. A trace table is only useful if you run the program after completing it and compare the Output column to what Python actually printed. If they differ, the discrepancy tells you exactly which line your mental model got wrong.

When your prediction is wrong, resist the urge to immediately change the table to match the output. Instead, re-run the trace mentally from the start and find the specific line where your reasoning diverged from the program's behaviour. That line is where your understanding has a gap. Fixing the table without finding the gap means the gap is still there, waiting to cause the next wrong prediction.

6   A Full Worked Example

Basic Idea

Here is a complete worked trace of a program that uses variables, a loop, and a conditional. Read through the table row by row before looking at the output.

count = 0
numbers = [4, 7, 2, 9]
for n in numbers:
    if n > 5:
        count = count + 1
print(count)

The variables are count, n, and the list numbers. Lists do not change in this program, so the table only needs columns for count and n.

LinecountnOutput
count = 00  
for n in numbers: — iteration 1 4 
if n > 5:4 > 5 → False. Skip the if block.
for n in numbers: — iteration 2 7 
if n > 5:7 > 5 → True. Enter the if block.
count = count + 11  
for n in numbers: — iteration 3 2 
if n > 5:2 > 5 → False. Skip the if block.
for n in numbers: — iteration 4 9 
if n > 5:9 > 5 → True. Enter the if block.
count = count + 12  
print(count)  2

The output is 2. The program counted how many numbers in the list were greater than 5 — and there were two of them: 7 and 9.

At a Deeper Level

Notice how the table makes the program's purpose visible. Before tracing, the program might look like a collection of syntax. After tracing, you can see what it is doing: it loops through a list and counts the values that meet a condition. The table reveals the intent, not just the mechanics.

This is one of the underrated benefits of trace tables. They do not just help you predict output — they help you understand what a program is for. When you are reading someone else's code, or returning to your own code after a break, a short trace often explains the logic faster than reading the variable names and comments.

Check Your Understanding
  1. In your own words, what is a trace table and why is it useful?
  2. Complete a trace table for this program. Write out every row.

    a = 8
    b = 3
    a = a - b
    b = a + 1
    print(b)
  3. Complete a trace table for this program. Show the condition evaluation row each time the if is reached.

    temperature = 18
    message = "unknown"
    if temperature >= 20:
        message = "warm"
    else:
        message = "cool"
    print(message)
  4. Complete a trace table for this program. Label each loop iteration.

    result = 1
    for i in range(1, 4):
        result = result * i
    print(result)

    What does this program calculate? Can you name it?

  5. A classmate traces the program in question 4 and writes i = 1, 2, 3, 4 for the loop iterations. What mistake have they made, and how would you explain the correct behaviour of range(1, 4)?
  6. After completing a trace table, you run the program and the output is different from what your table predicted. Describe the process you would use to find where your trace went wrong.