Skip to content
JavaScript js getting-started 5 min read

The Console & Debugging Basics

Every developer writes broken code—what separates beginners from professionals is how quickly they can find and fix it. The browser console and the DevTools debugger are your two most powerful tools for understanding what your code is actually doing, versus what you assumed it was doing. This page gives you a practical tour of the console API, breakpoints, the debugger statement, and how to read a stack trace so an error stops being a wall of red text and becomes a map to the bug.

Opening the console

In any modern browser (Chrome, Edge, Firefox, Safari) press F12 or Ctrl+Shift+I (Cmd+Option+I on macOS) to open DevTools, then click the Console tab. You can type and run JavaScript there interactively against the current page. In Node.js, console output goes straight to your terminal.

The console is a REPL: type an expression, press Enter, and you see the result. It is the fastest way to test a one-liner without editing a file.

The console methods you will actually use

console.log is the workhorse, but the console object has several methods that communicate intent and render output differently. Choosing the right one makes your logs scannable.

console.log("Plain message");
console.info("Informational note");
console.warn("Something looks off");
console.error("Something failed");

console.warn and console.error are styled distinctly (yellow and red) and console.error includes a stack trace, so reserve them for real problems rather than routine logging.

You can pass multiple arguments—they are printed space-separated, and objects stay inspectable (expandable in the panel) instead of being flattened to text:

const user = { id: 7, name: "Ada", roles: ["admin"] };
console.log("Loaded user:", user);

Output:

Loaded user: {id: 7, name: 'Ada', roles: Array(1)}

Tables and groups

When you log an array of objects, console.table renders a real grid that is far easier to read than nested objects:

const users = [
  { id: 1, name: "Ada", active: true },
  { id: 2, name: "Linus", active: false },
];
console.table(users);

Output:

┌─────────┬────┬─────────┬────────┐
│ (index) │ id │  name   │ active │
├─────────┼────┼─────────┼────────┤
│    0    │ 1  │ 'Ada'   │  true  │
│    1    │ 2  │ 'Linus' │ false  │
└─────────┴────┴─────────┴────────┘

Use console.group / console.groupEnd to nest related logs under a collapsible label—handy inside loops or per-request logging:

console.group("Checkout");
console.log("Validating cart");
console.log("Charging card");
console.groupEnd();

Handy extras

A few more methods earn their place once you know them:

MethodWhat it does
console.assert(cond, msg)Logs msg only when cond is falsy
console.count(label)Prints how many times that line has run
console.time / timeEndMeasures elapsed time between the two calls
console.dir(obj)Shows a DOM node as a JS object, not as HTML
console.time("fetch");
await fetch("/api/data");
console.timeEnd("fetch"); // -> fetch: 142.3ms

Tip: Strip out console.log calls before shipping, or gate them behind a debug flag. Leftover logs leak internal data and clutter the console for anyone using your site.

Breakpoints and the Sources panel

Logging tells you values at fixed points. A breakpoint lets you pause execution and inspect everything—every variable in scope, the call stack, and the live DOM—at the exact moment a line runs.

Open the Sources panel (called Debugger in Firefox), find your file in the file tree, and click a line number in the gutter. A blue marker appears. When execution reaches that line, the page freezes. Now you can:

  • Hover over any variable to see its current value.
  • Read the Scope pane for all local, closure, and global variables.
  • Step through code with the controls: Step over (F10) runs the next line, Step into (F11) dives into a called function, and Resume (F8) continues until the next breakpoint.

Right-click a line number for advanced breakpoints, including a conditional breakpoint that pauses only when an expression is true—invaluable inside a long loop:

for (const order of orders) {
  // Conditional breakpoint here: order.total > 1000
  process(order);
}

The debugger statement

You can also set a breakpoint directly in code with the debugger statement. When DevTools is open, execution pauses on that line exactly as if you had clicked the gutter; when DevTools is closed, it does nothing.

function calculateDiscount(price, rate) {
  const discount = price * rate;
  debugger; // execution pauses here when DevTools is open
  return price - discount;
}

This is great for code that is hard to locate in the Sources tree, but never commit it—a stray debugger will freeze the page for users with DevTools open.

Reading a stack trace

When an error is thrown, the console prints the error name, message, and a stack trace: the chain of function calls that led to the failure, most recent first.

Uncaught TypeError: Cannot read properties of undefined (reading 'name')
    at formatUser (app.js:12:25)
    at renderList (app.js:34:18)
    at app.js:50:3

Read it top to bottom. The first line states what went wrong: a TypeError because you tried to read .name from undefined. The next line says where: formatUser in app.js, line 12, column 25—your starting point. The lines below trace how you got there: renderList called formatUser, and top-level code called renderList. Click any app.js:12:25 link to jump straight to that line in Sources.

Gotcha: The deepest frame is often inside a library you do not control. Scroll down to the first line that points at your file—that is almost always where the real fix belongs.

Best Practices

  • Reach for console.warn and console.error for problems and keep console.log for routine output, so severity is visible at a glance.
  • Prefer console.table for arrays of objects and console.group to organize noisy, repetitive logs.
  • Label your logs (console.log("user:", user)) instead of logging bare values you cannot identify later.
  • Learn breakpoints early—a single paused frame reveals more than a dozen console.log calls.
  • Use conditional breakpoints to catch one bad iteration in a large loop without stopping on every pass.
  • Always read a stack trace from the top, then jump to the first frame in your own code.
  • Remove debugger statements and debug logs before committing or deploying.
Last updated June 1, 2026
Was this helpful?