Debugging C# Programs
Separate compiler errors, runtime failures, and wrong results, then use evidence to locate causes instead of guessing at fixes.
Before this lesson
- Variables
- Conditions
- Loops
Distinguish compile-time, runtime, and logic problems
Read diagnostics from the first relevant error
Trace changing state and test boundary inputs
The short answer
Debugging is the process of finding why observed behavior differs from expected behavior. First classify the problem, reproduce it with a small input, inspect the earliest useful message or state, test one hypothesis, and verify the fix against normal and boundary cases.
Different failures require different questions
A compile-time error prevents a program from being built: syntax is invalid, a name is unknown, or types do not fit. A runtime exception occurs after execution starts, such as indexing beyond an array. A logic error produces a result but the result is wrong. The compiler cannot know that a discount rule or average formula contradicts your requirement.
Classifying the symptom narrows the investigation. For a compiler error, inspect the message and nearby code. For an exception, read its type, message, and stack trace. For wrong output, compare expected and actual state at each important step.
| Problem | Typical symptom | First move |
|---|---|---|
| Compile-time | No executable result | Read the first diagnostic and location |
| Runtime | Exception after starting | Read exception type and stack trace |
| Logic | Program finishes with wrong output | Trace values against a hand-worked example |
Start with a reproducible case
A useful bug report says which input produces which result and what should happen instead. ‘The total is broken’ is difficult to test. ‘Quantities 2 and 3 produce 5.00, but with price 4.00 the total should be 20.00’ gives you a repeatable case and an expected value.
Reduce the input while preserving the failure. Small cases are easier to calculate by hand and produce less output. Do not change five lines at once; each change creates another possible cause.
Trace state where it changes
Temporary Console.WriteLine statements can reveal the value before and after an update. A debugger provides breakpoints, step controls, watches, and variable inspection without changing output. Both approaches answer the same question: what did the program know at this moment?
For loops, print the counter, condition-relevant state, and accumulated result. For conditions, print the smaller Boolean facts. Remove noisy diagnostic output after the cause is understood, or replace it with deliberate application logging when it has ongoing operational value.
int total = 0;
for (int number = 1; number <= 3; number++)
{
total += number;
Console.WriteLine($"number={number}, total={total}");
}Expected output
number=1, total=1 number=2, total=3 number=3, total=6
Quick knowledge check
Answer before you reveal.
01A program compiles and prints 12 instead of the expected 10. Which broad category is this?
A logic error: execution completed, but the implemented rule produced the wrong result.
02Why read the first compiler error before the tenth?
Later errors can be cascading consequences of an earlier missing token or invalid declaration.
Practice challenge
Now build it without copying.
The loop for (int i = 0; i <= names.Length; i++) crashes while printing an array. Reproduce the failure, record the final i value, correct the loop, and test arrays containing one and three names.
You are done when
- You can name the exception category and the invalid index
- The correction comes from the valid index range rather than a hardcoded length
- Both test arrays print every name exactly once
Stretch: Add a message for an empty array and verify that the loop body does not run.
Open challenge in playgroundLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Is debugging the same as testing?
Testing looks for evidence that behavior meets requirements; debugging investigates a known mismatch. They support each other but are not identical.
Should I catch every exception to keep the program running?
No. Catch only where you can add context, recover, or translate the failure meaningfully. Hiding an unexpected exception can corrupt state and make diagnosis harder.
When should I use a debugger instead of print statements?
Use a debugger when you need to step through branches or inspect several values without changing output. Print tracing remains useful in constrained environments and for a quick small case.