Grade by Grade
B. Software Development · Software Development: Problem Solving, Design, Testing and the Life Cycle

Debugging Methods: Tracing and Printing Variable State

B4.5About 70 minutes
Learning goals
  • I can follow a systematic debugging process: reproduce, locate, understand, fix, retest.
  • I can trace code by hand with a trace table to find a logic error.
  • I can add temporary debug output that shows the state of variables while a program runs.
  • I can choose a debugging method that suits the bug, including narrowing down where the bug is and using a debugger's breakpoints.

Words to know

bug
A mistake in a program that makes it behave wrongly.
debugging
Finding, understanding and fixing bugs.
logic error
A bug where the program runs without crashing but gives the wrong result.
trace table
A table with one column per variable (and output), filled in line by line as you run the code by hand.
program state
The values of all the variables at one moment while the program runs.
debug print
A temporary print statement added to show the program state, removed once the bug is fixed.
breakpoint
A marked line where a debugger pauses the program so you can inspect the variables and then step through line by line.

Being a detective

Your test plan from the last lesson says FAIL. Now what? Staring at the code and changing things at random rarely works. Good programmers debug like detectives: they gather evidence about what the program is really doing, form a theory, and test it.

The curriculum names two key methods: manual code tracing and extra code to output the state of variables. In Programming Basics you met the three kinds of errors (syntax, run-time and logic) and learned to read error messages. Logic errors are the hardest, because there is no error message at all. That is where these methods shine.

1. Reproducemake it happen again2. Locatetrace or print3. Understandwhy is it wrong?4. Fixchange one thing5. Retestrerun ALL testsstill failing?go round again
A systematic debugging process. Locating the bug often takes the most time; the fix is often one small change. Always retest everything afterwards.

Step by step

  1. Reproduce: find an input that always shows the bug (your failed test is perfect). A bug you cannot repeat is very hard to fix.
  2. Locate: find where the program's state first goes wrong. Trace it by hand, or print the variables.
  3. Understand: explain why it goes wrong. If you cannot explain it, you are not done.
  4. Fix: change one thing at a time.
  5. Retest: rerun the failed test and all the others (regression testing).

Method 1: Manual code tracing

Pretend to be the computer. Make a trace table with a column for each variable and for output. Go through the code line by line, writing each new value in its column. Compare with what should happen.

Worked example

Example 1: Tracing a loop

This program should add the odd numbers from 1 to 7 (1 + 3 + 5 + 7 = 16), but it prints 9. Trace it to find the bug.

  1. Before the loop: n = 7, total = 0.
  2. range(1, n, 2) with n = 7 gives i = 1, 3, 5. Pass 1: total = 1. Pass 2: total = 4. Pass 3: total = 9.
  3. The loop ends. Output: 9. The trace shows that i never takes the value 7.
  4. Understand: range stops before its end value, so range(1, 7, 2) excludes 7.
  5. Fix: range(1, n + 1, 2). Retest: 1 + 3 + 5 + 7 = 16 ✓, and also n = 1 (expected 1) and n = 8 (expected 16).

The bug is an off-by-one error in the range. After the fix the program prints 16.

n = 7
total = 0
for i in range(1, n, 2):
total = total + i
print(total)

The buggy version. Output: 9 (expected 16).

itotalstart–0pass 111pass 234pass 359after loop59The loop never sees i = 7, because range(1, 7, 2) stops before 7.Expected 16, got 9: the trace shows exactly where the 7 went missing.
The trace as boxes: the values of i and total after each pass. Seeing every value makes the missing 7 obvious.
n = 7
total = 0
for i in range(1, n + 1, 2):
total = total + i
print(total)

The fixed version. Output: 16.

LinentotaliOutput
17
270
3-4 (pass 1)711
3-4 (pass 2)743
3-4 (pass 3)795
57959
The same trace as a trace table. Notice that after the loop, i keeps its last value, 5.

Method 2: Output the state of variables

For longer programs, let the computer do the tracing. Add temporary print statements that show the important variables at key points: inside a loop, before and after a calculation, at the start of a function. Label them clearly (for example start with DEBUG) so you can find and remove them later.

Worked example

Example 2: Debug prints find a bad starting value

largest(values) should return the biggest number in a list. It works for [4, 19, 7] but returns 0 for [−8, −3, −5] instead of −3. Use debug output to locate the bug.

  1. Reproduce: largest([-8, -3, -5]) → 0. Expected −3.
  2. Add a debug print inside the loop showing v and biggest on every pass.
  3. Read the evidence: biggest is 0 on every pass and never changes, because no item is greater than 0.
  4. Understand: starting biggest at 0 assumes there is at least one item above 0. That is false for all-negative lists.
  5. Fix: start with a real item, biggest = values[0]. Remove the debug print. Retest both lists (and a one-item list).

The debug output shows biggest stuck at 0. Starting with the first item fixes it: the function now returns −3.

def largest(values):
biggest = 0
for v in values:
print("DEBUG v =", v, "biggest =", biggest)
if v > biggest:
biggest = v
return biggest
print(largest([-8, -3, -5]))

Output: three debug lines (DEBUG v = -8 biggest = 0, DEBUG v = -3 biggest = 0, DEBUG v = -5 biggest = 0) and then 0. The evidence shows biggest never changes.

def largest(values):
biggest = values[0] # start with a real item
for v in values:
if v > biggest:
biggest = v
return biggest
print(largest([-8, -3, -5]))
print(largest([4, 19, 7]))

Output: -3 / 19 (one line each)

More debugging tools

  • Narrow it down (divide and conquer). Print the state halfway through the program. If it is already wrong there, the bug is in the first half; otherwise it is in the second half. Keep halving.
  • Test functions alone. A modular program lets you call one function with known inputs to see whether it is the culprit.
  • Use the debugger. IDEs such as IDLE, Thonny and VS Code have a debugger: set a breakpoint on a line, run, and the program pauses there so you can see every variable; then step one line at a time. It is a trace table that fills itself in.
  • Rubber duck debugging. Explain the code line by line, out loud.
  • Take a break. Fresh eyes really do spot bugs faster.
  • Keep a record. Write a short bug report for each bug: the input, expected and actual results, the cause and the fix. Teams use these to avoid repeating mistakes.
Just for fun

In 1947, the team running the Harvard Mark II computer (Grace Hopper was part of it) found a real moth stuck in one of the machine's relays. They taped it into their logbook with the note "First actual case of bug being found." Engineers already used the word "bug" for faults; the moth just made it famous.

Hands-on activity

Bug hunt

You need: a computer with Python, and a family member or friend to plant bugs.

  1. Take a working program you wrote in this unit (the ticket price, the sorted insert or the prime test). Save a copy.
  2. Ask your helper to change one small thing without telling you: a < to <=, a + 1 removed, a variable name swapped, a line moved. (If no helper is available, ask a friend to send you their buggy program, or swap with a classmate.)
  3. Run your test plan to reproduce the bug.
  4. Find it twice: first with a trace table on paper, then with debug prints. Which was faster?
  5. Write a bug report: input, expected, actual, cause, fix. Fix it and rerun all tests.
  6. Bonus: open the program in IDLE or Thonny, set a breakpoint inside the main loop and step through it.

What to look for: the bug is found by evidence, not by guessing; the report explains the cause; all tests are rerun after the fix.

Your turn

1

List the five steps of the debugging process in this lesson.

2

What is a trace table?

3

Trace: x = 10 then while x > 1: x = x // 2, then print(x). What values does x take, and what is printed?

4

Trace and give the output: total = 0, for k in range(3): total = total + k * k, print(total).

5

Write a debug print you could add inside a loop that uses the variables count and word.

6

In Example 2, why did largest work for [4, 19, 7] but not for [−8, −3, −5]?

7

Why should you rerun all the tests after a fix, not just the one that failed?

More practice: warm-up

1

Is a logic error reported by Python with an error message?

2

What is a breakpoint?

3

Why start debug prints with a label such as DEBUG?

4

What should you do first when a bug is reported?

5

Trace: count = 0, then for each word in ["ski", "skate", "sled", "snow"], add 1 to count if len(word) > 4. Final count?

More practice: on your own

1

This code is meant to swap a and b: a = 3, b = 8, a = b, b = a, print(a, b). Trace it, explain the bug and fix it.

2

A program computes a price: price = 50, price = price * 1.13, price = price - 5, then prints round(price, 2). The spec says the $5 coupon comes off before tax (expected $50.85). What does it print, and how would debug prints help?

3

Describe how to use "divide and conquer" to find which of 200 lines contains a bug.

4

A countdown(n) function should print n down to 1, then "Liftoff!". For n = 3 it prints only "Liftoff!". The loop is while n < 0:. Find and fix the bug.

5

When is a trace table better than debug prints, and when are debug prints better?

Challenge

1

This function should count the vowels in a word, but count_vowels("Ottawa") returns 2 instead of 3: count = 0, for ch in word: if ch in "aeiou": count = count + 1, return count. Find the bug with a trace and fix it.

2

A teammate fixes bugs by changing random lines until the output looks right. Explain two dangers of this approach.

3

A program works when run normally but a user reports wrong totals "sometimes". Describe how you would reproduce and locate this bug.

Check your understanding

Quiz

1.

Which method involves running the code by hand and recording every variable's value?

2.

What does this print? total = 0 / for i in range(1, 5): / total = total + i / print(total)

3.

What is the main purpose of a debug print?

4.

A largest() function starts with biggest = 0. For which list does it fail?

5.

After fixing a bug, you should:

6.

In a debugger, what happens at a breakpoint?

For parents and teachers

Big idea: B4.5 asks students to use a variety of debugging methods, including manual code tracing and extra code that outputs the state of variables.

How to help: When your teen is stuck, ask "What did you expect, and what happened?" then "What is the value of each variable at this line?" Encourage them to write a trace table rather than guessing. The bug-hunt activity is fun to do together: you plant one small change and they find it.

Common difficulties:

  • Changing code at random instead of gathering evidence.
  • Forgetting that range stops before its end value (a very common off-by-one bug).
  • Leaving debug prints in the final program.
  • Fixing one test and not rerunning the rest.