First exams 2027 · SL & HL

B2.1.4 · Programming fundamentals

Debugging techniques: finding a bug that doesn't crash anything

B2.1.3 was about failures a program can name and catch. This statement is about the other kind: code that runs to completion without a single error, and simply produces the wrong answer.

Core for SL & HL Typically 3–5 marks · AO2/AO3 Command term: construct, use

Explainer

Fairbank Canteen's order till is supposed to add up the price of everything a student buys and print the total. It runs perfectly, no crash, no error message, prints a confident-looking number. That number is simply wrong. There is nothing here for try/except to catch, since Python never objects to what the code is doing, it just does the wrong thing correctly. Finding a bug like this needs a different toolkit entirely.

Df

A bug is an error in a program's logic that produces incorrect behaviour without necessarily causing a crash. Debugging is the systematic process of locating exactly where a program's real behaviour diverges from what it was intended to do, then fixing it.

Systematic beats random

Changing lines at random until the output happens to look right is not debugging, it's guessing, and it tends to leave a second bug behind for every one it accidentally fixes. Every technique below works the same basic way: form a prediction about what a variable should hold at some point, then check it against what the program actually holds there, and narrow in on the exact line where the two stop matching.

Explainer

Four named techniques

The syllabus names four. Each answers the same question, "what does this variable actually hold right now", by a different route.

1

Print statements. Temporarily insert print() calls at suspicious points in the code, run it, and read what actually gets printed. Cheap and needs no tool beyond the language itself, but the code has to be edited to add them, and edited again to remove them once the bug is found.

2

Trace tables. A table with one column per variable, filled in by hand line by line as the program is worked through. It's the disciplined, written-down record of tracing, and it's exactly the technique B2.1.1 already built one of, for a completely different reason (there, to check a scope trace was complete; here, to catch a WRONG value the moment it first appears).

3

Step-by-step execution. Running the program one line at a time, mentally or with a tool, and checking each line's actual effect against what you predicted before running it. A trace table is what you get when you WRITE DOWN what step-by-step execution shows you as you go.

4

Breakpoint debugging. Using a real debugger tool to pause a program's execution at one chosen line, then inspecting every variable's actual current value before choosing to continue. This page's own click-through stepper below is doing exactly that: pausing at a line you choose and showing you what every variable holds at that instant, which is the whole idea a breakpoint debugger automates for real running code.

Print statements

Real example: adding print(price, total) inside a suspect loop to see it change on every pass.

Advantage

  • Needs no separate tool (works anywhere the program itself runs, including inside this page's own console)

Disadvantage

  • Requires editing the source code twice (once to add the prints, once to remove them, and a forgotten one is now shipped debug output)
Breakpoint debugging

Real example: pausing execution right before a suspect line and inspecting every variable's live value, without changing a single character of the code.

Advantage

  • The source code is never touched (nothing to remember to remove afterward, and the exact same run you're debugging is the one that eventually ships)

Disadvantage

  • Needs a debugger tool available and set up (not always possible, e.g. a locked-down school machine or a bare command line)

Worked example

Hunting a silent total that's always wrong

Four prices should add up to 14. The till prints 4 instead, every single time, with no error anywhere. Four techniques applied to the same bug, one after another.

Applying all four techniques to one silent bug Step 1 of 6

Step 1. The bug, and why it's hard to spot by reading. The code reads perfectly reasonably: for every price, update the total. Nothing here looks obviously wrong on a first read.

prices = [3, 5, 2, 4] total = 0 for price in prices: total = price print("Final total:", total)
Output
Final total: 4
Expected 14 (3+5+2+4). Wrong, and silent, nothing crashed.

Step 2. Technique 1: print statements. Add a temporary print inside the loop to see what's actually happening on every pass, not just at the end.

for price in prices: print("price:", price, "| total before:", total) total = price print("price:", price, "| total after: ", total)
What the prints reveal
price: 3 | total before: 0 | total after: 3
price: 5 | total before: 3 | total after: 5
total after NEVER carries the previous total forward, it always matches price exactly

Step 3. Technique 2: a trace table. The same insight, written down in the disciplined format from B2.1.1, one column per variable, filled by hand.

Linepricetotal
before loop0
pass 133
pass 255
pass 322
pass 444

The total column never accumulates, each row simply overwrites the last. That pattern, visible at a glance in the table, is the bug.

Step 4. Technique 3: step-by-step execution. Narrate each line as it runs and compare it against what SHOULD happen. The exact moment prediction and reality diverge is the bug.

Predicted: total = total + price, so total becomes 0 + 3 = 3 Actual line: total = price Actual result: total becomes 3 (matches by coincidence, since total started at 0) Predicted (pass 2): total should become 3 + 5 = 8 Actual line: total = price Actual result: total becomes 5, NOT 8 -- prediction and reality diverge HERE

Pass 1 hides the bug by accident (0 + 3 happens to equal 3). Pass 2 is where step-by-step execution catches it for certain.

Step 5. Technique 4: breakpoint debugging. A real debugger would pause execution right before the total = price line on pass 2, showing total = 3, price = 5 in its variables panel. Stepping to the next line, without editing any code, shows total become 5, not 8. Same discovery as step 4, without narrating it by hand.

Step 6. The fix, confirmed, and the temporary prints removed. total = price should have been total = total + price. Once found, ALL the diagnostic print statements from step 2 get removed too, they were only ever there to help find this.

for price in prices: total = total + price print("Final total:", total)
Output, fixed
Final total: 14

Try it yourself

Run the buggy till, then fix it

The diagnostic print statements from step 2 are already in place, run it and watch total get overwritten instead of growing. Then apply the one-line fix yourself.

main.py
Loading Python engine…
Output
Press Run to see the output here.

Running in offline fallback mode. The full Python engine couldn't load (blocked school network, or no internet), so this console switched automatically to a small hand-written engine. It runs this page's loop and print statements exactly, but it isn't full Python.

Check your understanding

A program crashes with no error message useful for working out what went wrong, and you want to inspect one variable's value at one exact line, without editing the source code at all. Which technique fits best?

Common pitfall

A well-known weak-answer pattern for B2.1.4-style debugging questions

RecurringThe most common weak answer names all four techniques with no distinction between them, or names only one, when the question actually asks WHICH technique suits a described situation and WHY. Listing the four terms is not the same as choosing between them.

The second common gap is treating a trace table and step-by-step execution as interchangeable synonyms. They are closely related but distinct: step-by-step execution is the ACT of working through the code one line at a time, a trace table is the WRITTEN RECORD of doing so, one column per variable. A strong answer names both correctly rather than folding them into one idea.

A third real mistake, this time a coding habit rather than an exam one: leaving temporary print() debugging statements inside code that gets submitted or shipped. They were only ever meant to be diagnostic, and should be removed once the bug they were added to find has actually been fixed.

Answer frame that scores: name the SPECIFIC technique that fits the described symptom → state what makes it suited to that symptom rather than another one → if code is shown, apply the technique and state the exact line where the bug is found.

✕

What a weak answer looks like: "You could use print statements, trace tables, breakpoints or step through the code to debug the program." True, but answers nothing about which one this situation actually calls for.

✓

What it should look like: "Breakpoint debugging suits this best, since it lets you inspect the variable's value at that exact line without editing the source code, unlike print statements which require adding and then removing temporary lines." Names the technique, and justifies the choice against the actual symptom.

Revision questions

Past-paper style. Write an answer, then reveal the marking points and mark yourself.

Question 1 · 2 marks

[2]Distinguish between a trace table and step-by-step execution.

Question 2 · 3 marks

[3]A program runs with no error but produces the wrong output. Explain why exception handling cannot help find this kind of bug, and identify a technique that could.

Question 3 · 5 marks

[5]Construct a trace table for a short faulty program, identify the line where the fault first appears, and state the fix.

Flashcards

Active recall for this statement’s key terms.