Debugging JavaScript Beyond console.log
Chrome DevTools breakpoints, conditional breakpoints, the debugger statement, network debugging, and the mindset that makes debugging faster.
Everyone starts debugging with console.log. And honestly, it works. For simple stuff, throwing a log statement in and checking the output is the fastest path to understanding what's happening. No shame in it.
But there's a ceiling. When you're dealing with race conditions, complex state, or a bug that only reproduces under specific conditions, console.log starts to feel like searching a dark room with a lighter. You need better tools.
Chrome DevTools Breakpoints
Open DevTools (F12), go to the Sources tab, find your file, and click on a line number. That's a breakpoint. When execution hits that line, everything pauses and you can inspect every variable in scope.
What makes this better than console.log: you don't need to know in advance what to log. You can explore the entire call stack, hover over variables, and step through execution line by line.
- Step Over (F10): Execute the current line and move to the next one
- Step Into (F11): If the current line calls a function, jump inside it
- Step Out (Shift+F11): Finish the current function and return to the caller
- Continue (F8): Resume execution until the next breakpoint
Conditional Breakpoints
Right-click a line number in DevTools and select "Add conditional breakpoint." Enter a JavaScript expression. The breakpoint only triggers when that expression is truthy.
// Instead of:
if (user.id === 42) {
console.log('found the problematic user', user);
}
// Set a conditional breakpoint on the relevant line with:
// user.id === 42
This is incredibly useful when you're debugging a loop that runs 10,000 times but only breaks on one specific iteration. No code changes, no redeployment.
Logpoints: console.log Without Touching Code
Right-click a line number, choose "Add logpoint." Type a message with expressions in curly braces: User: {user.name}, Status: {response.status}. DevTools logs to the console every time that line executes — no code modification required.
This is console.log for people who are tired of cleaning up console.log statements before committing.
The debugger Statement
Drop debugger; anywhere in your code. When DevTools is open and execution hits that line, it pauses as if you set a breakpoint. Useful when you're not sure which file to set the breakpoint in, or when the code is dynamically generated.
function processPayment(order) {
debugger; // execution pauses here when DevTools is open
const total = calculateTotal(order.items);
// ...
}
Just don't commit it. Seriously. Add a linter rule: no-debugger.
Network Tab for API Issues
Half the bugs in web apps are data problems — the API returns something unexpected, a request fails silently, or the payload is wrong. The Network tab shows every HTTP request with the full request/response details.
Filter by XHR/Fetch to see only API calls. Click a request to see:
- Headers: Was the auth token sent? Correct Content-Type?
- Payload: What did the client actually send?
- Response: What came back? Is it the shape you expected?
- Timing: Did the request take 50ms or 5 seconds?
For intermittent issues, check "Preserve log" so the network history survives page navigations.
Node.js Debugging with --inspect
For server-side JavaScript, console.log debugging is even more tempting because there's no browser UI. But Node has a built-in inspector:
node --inspect server.js
# Or break on the first line:
node --inspect-brk server.js
Open chrome://inspect in Chrome, click the inspect link for your Node process, and you get the full DevTools experience for server-side code. Breakpoints, variable inspection, the call stack — everything.
VS Code also connects to the Node inspector natively. Add a launch configuration and you can debug without leaving your editor.
React DevTools (and Vue DevTools, etc.)
Framework-specific devtools are worth installing. React DevTools lets you inspect the component tree, see props and state for any component, and track what caused a re-render. The Profiler tab identifies performance bottlenecks.
The Components tab is especially useful for debugging: find a component, edit its state directly in the panel, and watch the UI update. Way faster than modifying code and refreshing.
The Debugging Mindset
Tools aside, debugging is a thinking process. Here's the loop:
- Reproduce: If you can't trigger the bug reliably, you can't fix it. Find the exact steps or conditions.
- Isolate: Narrow down where the problem is. Is it the API response? The state update? The rendering logic? Use binary search — comment out half the code and see if the bug persists.
- Understand: Before you fix it, understand why it's broken. The first fix that comes to mind is often a band-aid.
- Fix: Make the smallest change that addresses the root cause.
- Verify: Confirm the fix works and doesn't break anything else. Write a test if one doesn't exist.
Practice Makes It Faster
Debugging is a skill, and like any skill, it gets better with reps. Working through coding challenges on CodeUp is a great way to build that muscle — you hit bugs, you fix them, you build intuition for where things go wrong. That pattern recognition is what separates a 10-minute debug session from a 4-hour one.