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 true data 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. This is a generic debugger concept, not a promise that every IDE implements it. In base PyCharm, a Watch is an expression whose value is displayed after execution has already paused at a breakpoint; it does not itself suspend the program when a Python variable changes. A Watch expression plus line or conditional breakpoint can approximate the investigation, but it is not the same as a true data watchpoint.
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:| position | array[position] | array[position] < 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.if found:, immediately after the loop and before either return branch is selected:"Muffin" genuinely reaches the breakpoint on line 9. At that reachable paused state, found == False and index == 5: indexes 0–4 have been checked, then the loop condition 5 < 5 becomes false before index 5 is compared. Execution therefore skips line 10 and continues to line 12, returning -1. These live values point straight at line 4's off-by-one loop bound.
smallest itself rather than on any particular line. This mock illustrates that concept; it is not a literal base-PyCharm screen or walkthrough:smallest's initial value becomes 0, so there is no later watchpoint suspension during the six loop iterations. That absence is diagnostic: line 5 is never reached, pointing at line 4's comparison. In base PyCharm, use the closest built-in workflow instead: add smallest as a Watch expression, pause at a line breakpoint on line 6, and inspect its displayed value of 0; a conditional breakpoint on line 5 can also be armed to pause if the update ever becomes reachable. This uses a Watch plus breakpoints and is explicitly not a true suspend-on-assignment watchpoint.
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 need their own explicit conceptual treatment. Keep the tool distinction accurate: base PyCharm's Watches pane displays expressions once a breakpoint has already suspended execution; it does not provide the true suspend-whenever-this-Python-variable-changes behaviour illustrated conceptually in Example 4.
Suggested timing: 5 min warm-up + vocab · 25 min notes + debug-grid walkthrough · 35 min examples 1–4 · 10 min "now you try" · 40 min Task Set A · Task Set B as homework/extension. Demonstrate Example 3's reachable line-9 breakpoint live in PyCharm. For Example 4, demonstrate a smallest Watch expression while paused at line 6 and optionally a conditional breakpoint on line 5; explicitly tell pupils that this built-in combination approximates the investigation but is not a true data watchpoint.
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.