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
- Reproduce the bug reliably.
- Form a hypothesis — "I think the loop runs one extra time."
- Test it — add a log or change the code to confirm.
- Fix — if the hypothesis is confirmed, make the targeted change.
- 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 Resources
Related Snippets
- JavaScript Array Methods
Understanding built-in methods reduces the bugs you write by hand.
Related Cheatsheets
- JavaScript Cheatsheet
Reference for console methods and error handling syntax.
Look up unfamiliar terms
A beginner-friendly glossary explains jargon in plain English — variables, functions, DOM, Promise, and more.
Build real projects
Apply what you learned by building guided projects with starter code and solutions.
Decode error messages
Plain-English explanations of common errors — what they mean, why they happen, and how to fix them.