Debugging and Testing for Beginners

A bug is simply the gap between what a program actually does and what the programmer believed it would do, and debugging is the disciplined process of shrinking that gap by gathering evidence instead of guessing. The first and most common mistake beginners make is changing code at random hoping something works, which occasionally succeeds by luck but teaches nothing and usually introduces a second bug alongside the first; the alternative, tracing execution, means stepping through the program's actual behavior one line at a time — either with a debugger tool that pauses execution and lets a programmer inspect every variable's current value, or with the much older and still perfectly valid technique of inserting print statements at key points to reveal what the program believes is true at that moment, compared against what should be true. A syntax error is the easiest category to fix: the program violates the language's grammar rules and won't even run, and the compiler or interpreter usually reports the exact line. A runtime error happens while the program is executing — dividing by zero, or accessing position 10 in a list that only has 5 elements — and the program crashes with a specific, traceable error message. A logic error is the hardest category by far: the program runs successfully, produces an answer, and that answer is simply wrong, with no error message at all to point toward the cause, because nothing about the execution itself was invalid — only the underlying plan, going back to the computational-thinking topic earlier in this series, was flawed. Assertions are a tool for catching logic errors early and precisely: a programmer writes `ASSERT balance >= 0` directly into the code as a stated guarantee about what should always be true at that exact point, and if that guarantee is ever violated while the program runs, it stops immediately with a clear, specific message rather than continuing to run on already-corrupted data and failing confusingly somewhere else, much later, far from the actual cause. Test cases formalize the whole process: a test case is a specific input paired with the expected correct output, and a thorough suite of them — including deliberately chosen edge cases like an empty list, a negative number, or the very largest value a data type can hold — catches regressions automatically every time the code changes, turning "did I break anything" from a hopeful guess back into a checked, repeatable fact. A calculator program, continuing the project example from the programming-fundamentals topic, needs test cases covering ordinary arithmetic, division by zero (a runtime error waiting to happen), and an unrecognized operator symbol (a logic error waiting to happen) before anyone should trust it actually works.

Program produces a wrong or crashing result
the gap between expected and actual behavior
Syntax error
violates language grammar — caught before running
Runtime error
crashes mid-execution — divide by zero, bad index
Logic error
runs fine, answer is simply wrong, no error message
Trace execution
debugger step-through, or print statements
Fix, then write a test case for it
so this exact bug can never silently return
Three Kinds of Bugs, Ranked by How Hard They Are to Find Only one of these gives you an error message to work from Syntax error won't even run compiler points at the exact line easiest Runtime error crashes while running divide by zero, bad list index medium Logic error runs fine, produces a wrong answer, no error at all hardest the plan was wrong, not the execution — this is why computational thinking matters
A Test Case Is an Input Paired With a Known Answer A calculator's test suite, including the edge cases that actually matter input expected output 4 + 5 9 10 / 2 5 7 / 0 runtime error, handled cleanly 4 % 5 logic error, unknown operator -5 + -5 -10, edge case: negatives a suite like this runs automatically on every code change turning "did I break anything" into a checked fact, not a guess the two red rows are deliberately chosen to fail, and must fail safely

This discipline matters more, not less, as the curriculum moves forward: memory and program execution introduces an entire new class of bug (dangling pointers, memory leaks) invisible to simple print-statement tracing, and software engineering later in this series builds automated testing into an ongoing team practice rather than something done once at the end.

Take debugging and testing for beginners further

Ask the copilot about this term, or have our engineers assess it against your process.