BuildQuill
C# learning path
8 of 25
Lesson 8 of 25
Lesson 8Beginner16 min

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.

01

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.

ProblemTypical symptomFirst move
Compile-timeNo executable resultRead the first diagnostic and location
RuntimeException after startingRead exception type and stack trace
LogicProgram finishes with wrong outputTrace values against a hand-worked example
02

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.

03

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.

C#
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
04

Boundary cases expose hidden assumptions

Test the smallest valid value, largest valid value, just outside each boundary, an empty collection, and one normal middle value. For a rule from 1 through 10, try 0, 1, 5, 10, and 11. This is more informative than repeating several comfortable middle values.

After fixing the failure, run the original case and nearby cases. A change that fixes one input by hardcoding its answer is not a real fix. The goal is to correct the rule that connects the whole valid input space to the output.

Good habits

  • Copy exact diagnostics; do not paraphrase away useful names and line numbers.
  • Ask what evidence would disprove your current theory.
  • Keep a small failing example before making a structural refactor.
  • Warnings often reveal real risk even when the build succeeds; read them.

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 playground

Lesson checkpoint

One small step locks it in

Mark this lesson complete, then keep the momentum going.

Complete and continue

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.