Software Design & Development · Testing

SDD17 — Debugging

📅 Tue 22 Sep 2026 · P3 (single)
~60 minutes
Learning intentions
Success criteria
Warm up — recap from SDD16
Answer all three questions, then check your answers.
Question 1
What term describes an error that lets a program run to completion without crashing, but produces a wrong result?
Question 2
Which category of test data is an invalid input a program should reject or handle safely?
Question 3
Why must a test plan's expected result be worked out before the program is run?

Key vocabulary

Dry run
Manually working through a program's code step by step, by hand, without actually running it on a computer.
Trace table
A table used during a dry run to record the value of every relevant variable at each step of the program's execution.
Breakpoint
A marker placed on a specific line of code that pauses the program's execution whenever that line is about to run.
Watchpoint
A marker placed on a specific variable that pauses execution whenever that variable's value changes or meets a condition, regardless of which line causes it.

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.

Dry run
Working through code by hand, step by step, without running it on a computer at all.
Trace table
A table recording every variable's value at each step of a dry run — one row per iteration.
Breakpoint
Pauses execution at a specific line, whenever that line is about to run.
Watchpoint
Pauses execution when a specific variable changes, regardless of which line causes it.

Worked examples

Example 1 — Dry run and trace table locating SDD14's bug
1
SDD16 established that 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:
iarray[i]array[i] < smallest?smallest (after)
030False0
145False0
220False0
315False0
460False0
518False0
Confirmed by an actual Python run: 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.
Example 2 — A second bug: an off-by-one loop bound
1
A different bug in a linear search implementation:
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")
2
The loop condition is 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.
Example 3 — Using a breakpoint
1
To investigate Example 2's bug, a breakpoint is placed on the return index line — the point right before the function's result is decided:
linear_search_buggy.py
1def linearSearchBuggy(array, target):
2    index = 0
3    found = False
4    while index < len(array) - 1 and not found:
5        if array[index] == target:
6            found = True
7        else:
8            index = index + 1
9    if found:
10        return index
11    else:
12        return -1
When the breakpoint on line 10 is reached during a search for "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.
Example 4 — Using a watchpoint
1
Investigating Example 1's bug differently — a watchpoint is set on the variable smallest itself, rather than on any particular line:
find_minimum_buggy.py
1def findMinimumBuggy(array):
2    smallest = 0  ◆ watch: smallest
3    for i in range(len(array)):
4        if array[i] < smallest:
5            smallest = array[i]
6    return smallest
A watchpoint on 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.
Now you try
Would a breakpoint or a watchpoint be more useful for investigating why a variable called 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.
⚠️ Common mistakes — examiner feedback
📝 Exam tip

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

Task Set A — Core questions
Work through all questions, then check your answers.
Question 1
What term describes manually working through code step by step, by hand, without running it on a computer?
Question 2
What term describes the table used during a dry run to record every variable's value at each step?
Question 3
What is a breakpoint tied to?
Question 4
What is a watchpoint tied to?
Question 5
In Example 1's trace table, what value does smallest hold after all six iterations?
Question 6
Using Example 2's buggy linearSearchBuggy, what does linearSearchBuggy(itemNames, "Muffin") return, given "Muffin" is the last element?
Question 7
Explain one situation where a dry run/trace table would be more suitable than a breakpoint or watchpoint, and one situation where the reverse is true. (4 marks)
Question 8
A programmer knows a variable called total isn't updating correctly, but doesn't know which of several functions is responsible. Which technique is most directly suited to finding out?
Question 9
Complete a trace table (by describing each row) for linearSearchBuggy(itemNames, "Flapjack"), and explain why this particular search still returns the correct result despite the bug. (4 marks)
Question 10
Explain how debugging techniques relate to a test plan that has already identified a failing test case. (3 marks)

Task Set B — Extension

Task Set B — Extension · Beyond the specification
Longer written answers — no auto-check. Discuss your answers with your teacher.
Extension 1
Research "conditional breakpoints" (breakpoints that only pause when a stated condition is true, e.g. only when a loop counter reaches a specific value). Suggest why this would be especially useful inside a loop that runs many times.
Extension 2
SDD18 will cover five evaluation criteria (fitness for purpose, efficient use of coding constructs, usability, maintainability, robustness). Suggest how thorough debugging during development might affect at least two of these criteria.
Extension 3
Compare SDD14's 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.
📁 File this in OneNote under:
Higher Computing Science → Software Design & Development → SDD17
📌 Teacher notes — not for pupils

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.