Debugging with the console and DevTools

Read error messages and stack traces, log with purpose, and pause running code with breakpoints to see exactly what it is doing.

  • Beginner
  • 9 min read
  • Updated 17 Oct 2026

What you will learn

  • Set and use breakpoints
  • Read a stack trace
  • Choose the right console method for the job
  • Inspect variables with the Scope and Watch panes

Before you start

Every programmer writes bugs. What separates beginners from experienced developers is how quickly they find them. This lesson replaces guessing with a method: read the error, form a theory, then confirm it with logs or by pausing the code and looking inside. The examples use the quiz app and Chrome DevTools; Firefox and Safari have the same tools under slightly different names.

Open DevTools

Press F12, or Ctrl+Shift+I on Windows and Linux, Cmd+Option+I on macOS. The panels you need are:

  • Console: errors, warnings and your logs, plus a place to run JavaScript against the page.
  • Sources (called Debugger in Firefox): your files, breakpoints and step controls.
  • Elements (Inspector in Firefox): the live DOM.

Keep the Console open while you develop. Many bugs announce themselves there, and nobody sees them if it is closed.

Reading an error message

Break the quiz on purpose. In the Next button handler from lesson 16, change < to <=:

JavaScript
if (currentIndex <= questions.length) {
  showQuestion(currentIndex);
  questionText.focus();
} else {
  showResults();
}

Play through all three questions and press Next at the end. Nothing happens on the page, but the Console shows something like this:

Plain text
Uncaught TypeError: Cannot read properties of undefined (reading 'text')
    at showQuestion (quiz.js:31:42)
    at HTMLButtonElement.<anonymous> (quiz.js:94:5)

The line and column numbers depend on your file. Read the message in three parts:

  1. The error type: TypeError. The common ones are ReferenceError (a name that does not exist, often a typo), TypeError (using a value in a way its type does not allow, such as reading a property of undefined) and SyntaxError (code that cannot be parsed, so nothing in the file runs).
  2. The message: it was trying to read text from something that was undefined. So the question is not "why is text missing?" but "why is the thing before .text undefined?".
  3. The location: quiz.js, line 31, column 42. Click it to jump to that exact spot in the Sources panel.

Firefox words the same error as TypeError: can't access property "text", question is undefined. Different words, same facts.

Reading the stack trace

The indented lines under the message are the stack trace: the chain of function calls that led to the error, newest first.

  • at showQuestion (quiz.js:31:42): the error happened here, in showQuestion.
  • at HTMLButtonElement.<anonymous> (quiz.js:94:5): showQuestion was called from an anonymous function running for a button, the Next button's click listener.

Read it top to bottom: where it broke, then who called it, and who called that. The bug is often not on the top line. Here line 31 (question.text) is innocent; it received a bad index. The real mistake is in the caller, which let currentIndex reach 3 when the valid indexes are 0 to 2. Following the trace down is how you find that.

Named functions make traces easier to read than <anonymous>, which is a good reason to name your important handlers.

Logging with purpose

console.log is the quickest way to test a theory. Log a labelled object rather than a bare value, so you can tell your logs apart:

JavaScript
console.log({ currentIndex, total: questions.length });
// → { currentIndex: 3, total: 3 }

The shorthand { currentIndex } creates an object whose key is the variable's name. The console has more than log:

MethodUse it for
console.warn(), console.error()Messages that stand out, with a stack trace you can expand
console.table(questions)Arrays of objects, shown as a sortable table
console.group("Answer") and console.groupEnd()Grouping related logs so they can be collapsed
console.count("click")Counting how often a line runs
console.assert(score <= questions.length, "Score too high")Logging only when a condition is false
console.time("render") and console.timeEnd("render")Measuring how long something takes
console.dir(element)Showing a DOM element as an object with all its properties

A logged object is shown as it is when you expand it, not when it was logged. If it changes afterwards, the expanded view can mislead you. Log JSON.stringify(value) or a copy when the moment matters.

Remove debugging logs when you are done. They clutter the console and can expose data.

Pausing with breakpoints

Logging needs you to guess in advance what to print. A breakpoint pauses the code at a line so you can look at everything: every variable, the call stack and the page as it is at that moment.

Fix the <= back to <, then introduce a quieter bug: in the questions array, change the first answer to "const ", with a trailing space. Now a correct answer to question 1 is marked wrong, and there is no error at all. Logic bugs like this are where breakpoints shine.

  1. Open Sources and select quiz.js from the file tree (Ctrl+P or Cmd+P opens a file by name).
  2. Click the line number of const isCorrect = button.textContent === question.answer; in handleAnswer. A blue marker appears.
  3. Choose const in the quiz. The page freezes and the line is highlighted. That line has not run yet.

While paused, look at the right-hand side:

  • Scope lists every variable visible at this point: local ones such as button and question, then outer scopes, then globals.
  • Call Stack is the live version of a stack trace. Click a frame to see that function's variables.
  • Watch lets you add expressions that are evaluated at every pause. Add button.textContent and question.answer.

You can also hover over a variable in the code, or type expressions into the Console, which runs in the paused scope:

JavaScript
JSON.stringify(button.textContent)
// → "\"const\""
JSON.stringify(question.answer)
// → "\"const \""

JSON.stringify makes invisible differences visible: there is the stray space. Remove it from the array and the bug is gone.

Stepping through code

The toolbar above the Scope pane controls execution:

ButtonShortcutWhat it does
ResumeF8Run until the next breakpoint
Step overF10Run the current line, including any function it calls, and pause on the next line
Step intoF11Enter the function called on this line
Step outShift+F11Finish the current function and pause in its caller

Step over the isCorrect line and the Scope pane shows its new value. Step into markAnswer(button, isCorrect) to follow the call. Stepping one line at a time while watching the variables is the most reliable way to find where reality parts from your expectations.

Smarter breakpoints

Right-click a line number for more options:

  • Conditional breakpoint: pause only when an expression is true, for example currentIndex === 2. Ideal inside loops and handlers that run many times.
  • Logpoint: log a message without pausing and without editing your file, for example "answer", question.answer.

The Breakpoints sidebar also offers:

  • Event listener breakpoints: pause on any click, keydown or submit, which helps when you do not know which code handles an event.
  • Pause on uncaught exceptions: stop at the moment an error is thrown, with all the variables still available. Turn this on and repeat the <= bug: you will be paused on the failing line with question showing as undefined and index as 3.

In the Elements panel, right-click an element and choose Break on then subtree modifications or attribute modifications to find which code changes it.

The debugger statement

Writing debugger; in your code pauses execution at that line whenever DevTools is open. It does nothing when DevTools is closed. It is handy when a file is hard to find in Sources, but delete it before committing.

JavaScript
function handleAnswer(event) {
  debugger;
  // ...
}

Debugging in Node.js

The same tools work for scripts run with Node.js. Run node --inspect-brk script.js, then open chrome://inspect in Chrome and click inspect under your script. You get the Sources panel, breakpoints and stepping. Editors such as VS Code have a built-in debugger that does the same from the editor.

A debugging method

  1. Reproduce the bug reliably, with the smallest steps you can.
  2. Read the error type, message and stack trace in full.
  3. Form a theory about which value is wrong and where.
  4. Check it with a log or a breakpoint. Do not change code until you have evidence.
  5. Fix the cause, not the symptom, then repeat the steps to confirm.

Summary

  • An error message has a type, a description and a location; click the location to jump to the code.
  • A stack trace lists the calls that led to the error, newest first, and the real mistake is often further down.
  • Log labelled objects, and use table, group, count, assert and time where they fit.
  • Breakpoints pause code so you can inspect the Scope, Call Stack and Watch panes, then step over, into or out.
  • Use conditional breakpoints, logpoints, event listener breakpoints and pause on exceptions to find bugs faster.

Further reading