Skip to content

Debugging Basics

Learn practical debugging techniques — print debugging, reading stack traces, and forming hypotheses to fix bugs.

What you'll learn

  • What debugging is and the mindset behind it
  • How to use console.log to inspect values
  • How to read a stack trace to locate a bug
  • The hypothesis-test cycle for fixing bugs systematically

Concept

Debugging Basics

Debugging is the process of finding and fixing bugs. A bug is any difference between what your program does and what you intended. Debugging is not a separate activity from programming — it is most of programming. Becoming good at it is what makes you a productive developer.

The Debugging Mindset

Debugging is detective work. You observe a symptom (wrong output, a crash) and work backward to the cause. The key attitudes are:

  • Curiosity — "Why did it do that?" not "It should work!"
  • Patience — bugs are often subtle; rushing leads to wrong fixes.
  • Evidence over assumption — verify your beliefs with the program's actual behavior.

Print Debugging with console.log

The simplest technique: add console.log statements to print the value of variables at key points. This reveals what the program is *actually* doing, step by step.

function total(cart) {
  console.log("cart:", cart);          // what came in?
  let sum = 0;
  for (const item of cart) {
    console.log("item:", item, "sum so far:", sum);
    sum += item.price;
  }
  console.log("final sum:", sum);
  return sum;
}

Reading a Stack Trace

When an error occurs, the stack trace shows the chain of function calls that led to it. Read it top to bottom: the first frame is where the error happened; the frames below are who called whom. This pinpoints the location.

The Hypothesis-Test Cycle

  1. Reproduce the bug reliably.
  2. Form a hypothesis — "I think the loop runs one extra time."
  3. Test it — add a log or change the code to confirm.
  4. Fix — if the hypothesis is confirmed, make the targeted change.
  5. Verify — confirm the fix works and does not break other things.

Avoid "random changes and hope" — each fix should test a specific hypothesis. This turns debugging from a frustrating guessing game into a methodical process.

Example

              // Buggy function: should average a list of numbers
function average(numbers) {
  let sum = 0;
  for (let i = 0; i <= numbers.length; i++) {  // BUG: <= should be <
    sum += numbers[i];
  }
  return sum / numbers.length;
}

// Debugging: log each step
function averageDebug(numbers) {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    console.log(`i=${i}, value=${numbers[i]}, sum=${sum}`);
    sum += numbers[i];
  }
  console.log(`final sum=${sum}, count=${numbers.length}`);
  return sum / numbers.length;
}

console.log(averageDebug([10, 20, 30]));  // 20 — correct!
            

The first version has an off-by-one error (<= instead of <), which adds undefined to the sum and produces NaN. The debug version logs each step, making the boundary error obvious.

Try it

  • JSON Diff

    Diffing is a debugging technique: compare expected vs. actual output to pinpoint exactly where they differ.

Common mistakes

The mistake

Changing code randomly hoping the bug disappears.

The fix

Form a hypothesis first, then test it with a log or a small change. Random changes often introduce new bugs without fixing the original.

The mistake

Leaving debug console.log statements in finished code.

The fix

Remove or gate them behind a debug flag before shipping. Stray logs clutter the console and can leak sensitive data in production.

Related Snippets

Related Cheatsheets

Look up unfamiliar terms

A beginner-friendly glossary explains jargon in plain English — variables, functions, DOM, Promise, and more.

View glossary

Build real projects

Apply what you learned by building guided projects with starter code and solutions.

View projects

Decode error messages

Plain-English explanations of common errors — what they mean, why they happen, and how to fix them.

View errors