Debugging
By the end of this lesson
Find the cause of a defect methodically rather than by changing lines until it works.
Debugging is narrowing down where reality stops matching your expectation. It is a search, and searches are much faster when they are systematic.
The instinct when something breaks is to start changing things. That occasionally works and teaches you nothing, and it frequently introduces a second problem on top of the first.
A method that works reliably:
Reproduce it on demand
Find the exact input or steps that trigger it, every time. A bug you cannot reproduce cannot be verified as fixed.
State what you expected
Be specific. "It should show 118.00" is testable. "It should work" is not.
Find where expectation and reality diverge
Check a value in the middle. Correct there? The problem is later. Wrong? It is earlier. Each check halves the search space.
Form one hypothesis
"I think the tax is applied twice." One specific, checkable guess.
Test that one thing
Change one thing only. If it did not help, change it back before trying the next idea.
Confirm the fix and the surroundings
Re-run the original case, and check that nearby behaviour still works.
Two tools, both worth having
Printing values is the simplest approach and genuinely effective. Print what you believe a value to be at a few points and compare against what you expected.
decimal subtotal = CalculateSubtotal(items);
Console.WriteLine($"[debug] subtotal = {subtotal}");
decimal tax = CalculateTax(subtotal);
Console.WriteLine($"[debug] tax = {tax}");
decimal total = subtotal + tax;
Console.WriteLine($"[debug] total = {total}");- Label debug output so it is obvious and easy to find and remove later.
- Print at boundaries between steps, not on every line. You are looking for which step goes wrong.
A debugger does the same job without editing your code. You set a breakpoint on a line, run the program, and it pauses there so you can inspect every value in scope and step forward one line at a time.
Learning the debugger in your editor is worth an hour of your time. Stepping through a loop and watching values change is the single fastest way to build an accurate picture of what your code does.
Summary
- Reproduce first — a bug you cannot trigger reliably cannot be confirmed fixed
- State the expected value specifically, then find where reality diverges
- Halve the search space with each check rather than reading every line
- Change one thing at a time, and revert changes that did not help
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Challenge
Challenge
This method should return the average of a list of numbers, but it returns a value that is too low. Find the bug by reasoning, not by running it.
decimal Average(List<decimal> values) { decimal sum = 0; for (int i = 0; i < values.Count - 1; i++) { sum += values[i]; } return sum / values.Count; }
Show solution
The loop condition is i < values.Count - 1, so it stops one item early and the last value is never added to the sum. The division then uses the full count, so the result is too low.
Two defensible fixes: change the condition to i < values.Count, or use a foreach loop, which removes the opportunity for this mistake entirely.
There is a second problem worth noticing: if the list is empty, this divides by zero. A complete fix handles that case as well.
decimal Average(List<decimal> values)
{
if (values.Count == 0)
{
throw new ArgumentException("Cannot average an empty list.", nameof(values));
}
decimal sum = 0m;
foreach (decimal value in values)
{
sum += value;
}
return sum / values.Count;
}Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.