SDD17 — Debugging
- I can perform a dry run of code by hand, using a trace table to track variable values
- I can explain the difference between a breakpoint and a watchpoint
- I can use debugging techniques to locate exactly where in a program's logic a fault occurs
- I can complete a trace table showing every variable's value at each step of a dry run
- I can explain that a breakpoint pauses execution at a specific line, while a watchpoint pauses execution when a specific variable's value changes or meets a condition, regardless of which line that happens on
- I can use a failing test plan result as the starting point for locating a bug with these techniques
Key vocabulary
Locating faults with debugging techniques
From "it failed" to "here's why"
SDD16's test plan can only tell you that a test case failed — a pass/fail result on its own says nothing about where in the program's logic the fault actually lies. Debugging picks up exactly where testing leaves off: once a failing test case has been identified, these four techniques help locate the precise point where a program's actual behaviour starts to diverge from what was expected.
Dry runs and trace tables
A dry run means manually working through code step by step, by hand, exactly as the computer would — without actually running it. A trace table is the tool used to do this systematically: a table with one column per relevant variable, and one row per step (usually one row per loop iteration), recording every variable's value as it changes. Dry runs and trace tables were already used informally in SDD13/14's worked examples — SDD17 formalises this as a deliberate debugging technique, used specifically to find the exact iteration where a program's actual values first diverge from the expected ones.
Breakpoints
A breakpoint is a marker placed on a specific line of code within an IDE (such as PyCharm). When the program runs, execution pauses automatically the moment that line is about to be executed, allowing the programmer to inspect every variable's current value at that exact point before choosing to continue. Breakpoints are ideal when the programmer already has a good idea of which line is worth stopping at.
Watchpoints
A watchpoint is different, and is frequently confused with a breakpoint: rather than being tied to a specific line, a watchpoint is tied to a specific variable, and pauses execution whenever that variable's value changes (or meets some condition), no matter which line of code causes the change. Watchpoints are especially useful when the programmer knows which variable is behaving unexpectedly, but doesn't yet know which line is responsible for it — for example, watching a variable that is expected to change repeatedly throughout a loop, to immediately catch the moment (or the surprising absence of a moment) it actually does.
Worked examples
findMinimumBuggy (with smallest wrongly initialised to 0) returns 0 for stockLevels = [30, 45, 20, 15, 60, 18], when the true minimum is 15 — a logic error with no crash. A dry run traces exactly why:| i | array[i] | array[i] < smallest? | smallest (after) |
|---|---|---|---|
| 0 | 30 | False | 0 |
| 1 | 45 | False | 0 |
| 2 | 20 | False | 0 |
| 3 | 15 | False | 0 |
| 4 | 60 | False | 0 |
| 5 | 18 | False | 0 |
smallest never changes from its starting value of 0 across all six iterations, because every element in stockLevels is positive and therefore never "less than 0". The trace table makes the fault visible immediately — smallest's column never changes at all, which is itself the giveaway that something is wrong with its initial value, not with the loop logic.
def linearSearchBuggy(array, target):
index = 0
found = False
while index < len(array) - 1 and not found:
if array[index] == target:
found = True
else:
index = index + 1
if found:
return index
else:
return -1
itemNames = ["Chocolate", "Crisps", "Juice", "Flapjack", "Sweets", "Muffin"]
result = linearSearchBuggy(itemNames, "Muffin")index < len(array) - 1 instead of index < len(array) — this stops the loop one element too early, so the very last element ("Muffin", at index 5) is never actually compared to the target.result is -1 — confirmed by an actual Python run — even though "Muffin" genuinely is in the array. Searching for "Flapjack" (not the last element) still correctly returns 3, which is exactly why this bug is easy to miss during casual testing: it only ever affects searches for the array's final element.return index line — the point right before the function's result is decided:"Muffin", execution pauses — but inspecting the variables shows found is still False and index is 4, not 5. This immediately reveals the loop stopped one element too early, pointing straight at line 4's loop condition as the fault.
smallest itself, rather than on any particular line:smallest pauses execution only when its value changes. Running the buggy function against stockLevels, the watchpoint never triggers again after smallest's initial assignment on line 2 — it stays silent through all six loop iterations. This absence of any further pause is itself the diagnostic clue: smallest is supposed to change during the loop, and the fact that the watchpoint never fires again immediately shows the update on line 5 is never being reached, pointing straight at the comparison on line 4.
totalStock unexpectedly stays at 0 throughout a whole program, when you don't yet know which of several functions is supposed to update it? Explain your choice.
- Confusing breakpoints and watchpoints. A breakpoint is tied to a line; a watchpoint is tied to a variable. Describing a watchpoint as "a breakpoint on a variable" is a common simplification that loses the actual distinction being tested.
- Treating dry runs as optional once code compiles/runs. A dry run is precisely how a logic error (which produces no crash at all) gets caught — "the code runs" is not evidence a dry run is unnecessary.
- Building a trace table with missing rows. Every iteration of a loop needs its own row — skipping ahead to "the interesting bit" can hide exactly the iteration where the fault first appears.
- Assuming debugging techniques replace testing. Debugging techniques locate where a fault is, once testing has already revealed that one exists — they are not a substitute for having a test plan in the first place.
- Using a breakpoint when the faulty variable, not the faulty line, is what's actually unknown. If it's unclear which line is responsible, a watchpoint on the suspect variable is usually the more direct technique.
When distinguishing breakpoints from watchpoints, always name what each is tied to — a breakpoint to a line, a watchpoint to a variable — rather than describing them only in terms of "pausing the program," which doesn't distinguish them from each other. When completing a trace table, always fill in every column for every iteration, even when a value hasn't changed — a static value across several rows is itself often the evidence a question is looking for.
Task Set A — Core questions
smallest hold after all six iterations?linearSearchBuggy, what does linearSearchBuggy(itemNames, "Muffin") return, given "Muffin" is the last element?total isn't updating correctly, but doesn't know which of several functions is responsible. Which technique is most directly suited to finding out?linearSearchBuggy(itemNames, "Flapjack"), and explain why this particular search still returns the correct result despite the bug. (4 marks)Task Set B — Extension
findMinimum bug (wrong initial value) with this lesson's linearSearchBuggy bug (wrong loop bound). Suggest what these two bugs have in common, and why this connects back to SDD16's point about testing with extreme data.Higher Computing Science → Software Design & Development → SDD17
Double period. Four named techniques, not two — watchpoints must get their own explicit treatment and are frequently the weaker of the four in pupil recall, since PyCharm's UI surfaces breakpoints far more prominently by default.
Suggested timing: 5 min warm-up + vocab · 25 min notes + debug-grid walkthrough · 35 min examples 1–4, ideally demonstrated live in PyCharm (set an actual breakpoint and watchpoint on the two buggy functions, not just shown as the IDE mock) · 10 min "now you try" · 40 min Task Set A · Task Set B as homework/extension.
This lesson deliberately reuses both of this course's own real, actual-Python-verified bugs as its worked examples: SDD14's corrected findMinimum bug (Example 1, via trace table and watchpoint) and a new off-by-one linearSearchBuggy (Example 2, via breakpoint) — giving pupils two genuinely distinct debugging scenarios rather than two variations on the same fault.
Key misconception: pupils very reliably describe a watchpoint as "a breakpoint that watches a variable," which loses the actual distinction (line vs variable) being tested — worth explicitly contrasting Example 3 (breakpoint, one specific line) against Example 4 (watchpoint, one specific variable, line-agnostic) side by side.
SQA command words covered: explain, describe, complete a trace table.