P2.4 — Errors, Bugs & the Debugging Mindset¶
What you'll learn
- That errors are normal and are information, not failure.
- The two kinds of problem: an error that stops the program, and a logic bug that runs but is wrong.
- How to read an error message for where and what.
- The debugging loop: observe → guess the cause → test the guess — and
printas your main tool.
How it applies
- Reading errors is the actual skill. Every programmer, at every level, produces errors constantly. The difference between fast and stuck is not avoiding errors — it is reading them. An error message names where it happened and what went wrong; that is a head start, not a scolding.
- Logic bugs hide because nothing complains. The worst bugs run without any error and just
produce the wrong answer. There is no message pointing at them, so you find them by observing
the program's actual values — which is what
printis for. - Guessing randomly wastes time. Changing things at random sometimes works and teaches nothing. A deliberate loop — see the symptom, form one guess, test just that — finds the cause and builds understanding. (For a tester, this is the familiar reproduce → narrow → fix.)
printturns "I think" into "I see." Most debugging is discovering that a value is not what you assumed. Printing the value at the right spot replaces a guess with a fact, which is usually the whole fix.
Concepts¶
Errors are information¶
Code breaks. It breaks for everyone, all day. An error is the computer telling you it could not do what the code asked — and, crucially, telling you where and what. That is help, not failure. The goal is not to write code that never errors (impossible); it is to read the error and respond. A beginner who treats errors as normal feedback progresses; one who treats each as a personal failure stalls.
Two kinds of problem¶
- An error stops the program and shows a message. The computer literally cannot continue — a misspelled name, text used as a number (P1.3), an index past the end of a list (P2.2). These are the easy ones, because the program tells you something is wrong and roughly where.
- A logic bug is worse precisely because it does not error. The program runs to the end and
produces a wrong result — a score that is off, a loop that misses the last item, an
ifthat takes the wrong branch. Nothing complains; the answer is simply incorrect. You catch these by checking the program's actual output and values against what you expected.
Reading an error message¶
An error message has two parts worth finding immediately: where (a file and line number) and what (a short description). For example:
What: you tried to + a String and an int (the P1.3 trap). Where: line 7. You do not need to
understand every word — find the line, look at what it is combining, and the description usually points
straight at it. The named line is where the computer noticed the problem; sometimes the real cause is
a line or two earlier (a variable set wrong before it was used), so read around it.
The debugging loop¶
When something is wrong — error or logic bug — work the loop instead of flailing:
- Observe. What actually happened? Read the error, or print the values to see the real state. Get the symptom precise: "the total is 90 but should be 100," not "it's broken."
- Guess one cause. Form a single, specific hypothesis: "the loop stops one item early," or "this variable is text, not a number."
- Test just that. Make one change (or add one
print) that confirms or kills the guess. If it was right, you have the fix; if not, you have learned something and you guess again — now better informed.
One change at a time is the discipline. Change five things and something works, and you have no idea which — and no understanding gained.
Example
A logic bug, found by the loop — no error, just a wrong answer:
func _ready():
var total := 0
var prices := [10, 20, 30]
for price in prices:
total = total - price # BUG: should be +, not -
print(total) # prints -60, expected 60
Observe: it prints -60; you expected 60. No error fired — the code is valid, just wrong.
Guess: the total is going down, so maybe the combining step is subtracting. Test: add
print(total) inside the loop to watch it: it shows -10, -30, -60 — confirmed, it is shrinking
each pass. Look at the line: total = total - price. The - should be +. One-character fix, found
not by staring but by observing the value at each step. That is print-debugging: you saw the truth
instead of assuming it.
Walkthrough¶
Use your P1.1 script setup.
- Cause an error on purpose:
print("Score: " + 100)(text + number, P1.3). Run, read the message — find the what (operands) and the where (line). Fix it withstr(100). - Cause a "name not found" error: print a variable you never created (a typo like
scoer). Read how the message names the unknown identifier and the line. Fix the spelling. - Reproduce the logic-bug example (the
-that should be+). Run, see-60, then add aprintinside the loop to watchtotalchange. Use what you see to locate and fix the wrong operator. - Practice the loop out loud on that bug: state the observation ("−60, expected 60"), your one guess ("it's subtracting"), and the one test that confirmed it (the in-loop print). Naming the steps is the habit.
Optional sanity check
Take any small program you wrote earlier and add a print that reports a value you think you know
— a loop counter, a variable mid-calculation. Often it is exactly what you expected, which builds
trust; occasionally it is not, and you have just found a bug you did not know you had. Printing what
you assume is how assumptions get checked.
Self-check quiz¶
Q1 — The program stops and shows an error message. What is the first thing to do?
A. Change random lines until it works. B. Read the message for what went wrong and where (the line), then look at that line. C. Restart the computer. D. Delete the file and start over.
Reveal answer
B. An error message names the problem and the line — read it first; it is the fastest route to the cause. A is flailing (works sometimes, teaches nothing). C and D are over-reactions that discard information instead of using it.
Q2 — A program runs with no error but prints the wrong number. What kind of problem is this, and how do you find it?
A. An error; the message will point to it. B. A logic bug; since nothing complains, you find it by printing the actual values to see where they diverge from what you expected. C. Impossible — wrong output always causes an error. D. A hardware fault.
Reveal answer
B. Wrong-but-runs is a logic bug; there is no message, so you observe the real values (print-debugging) and compare to expectations to locate the divergence. A is wrong — logic bugs produce no error. C is false (most wrong output never errors). D misattributes a code bug to hardware.
Q3 — Why change only one thing at a time when debugging?
A. It is faster to change many. B. So that when the behavior changes, you know exactly which change caused it — preserving understanding. C. The editor only allows one change. D. It does not matter.
Reveal answer
B. A single change isolates cause and effect: if it fixes (or moves) the problem, you know why. Changing many at once can stumble into a fix but leaves you unable to say which change mattered — no understanding gained. A trades understanding for speed and usually costs more. C is false; D dismisses the core discipline.
Integration question¶
Q4 — open
A function is supposed to return the largest of two numbers, but max_of(3, 9) returns 3. The
code is func max_of(a, b): if a > b: return a (and nothing else). Walk the debugging loop on this:
what you observe, one specific guess, the one test that confirms it, and the fix — and say whether
this is an error or a logic bug, and how you can tell.
Reveal expected answer
Kind: it is a logic bug, not an error — the program runs and returns a value with no
message; it is just the wrong value. You can tell because nothing stopped or complained; the
output is simply incorrect. Observe: max_of(3, 9) returns 3, but the larger number is
9. Get it precise: it returns the wrong one specifically when the second argument is larger.
Guess (one, specific): the function only handles the case where a > b and has no path for
when b is larger — so when a > b is false, it falls off the end and returns nothing useful
(or a default), and certainly not b. Test: add a print(a, b) and a print("took if
branch") inside the if; calling max_of(3, 9) shows the if branch was not taken (since
3 > 9 is false), confirming there is no handling for that case. Fix: add the missing path —
else: return b (or an elif/else) so the function returns b when b is the larger:
The loop turned "it's broken" into a precise observation, one testable guess (the missing branch), a confirming test (the branch was skipped), and a one-line fix — and recognizing it as a logic bug told you to observe values rather than wait for an error that was never coming.