Most developers use console.log for debugging and call it a day. That works, but it leaves a lot of capability on the table. The browser DevTools debugger, performance profiler, and network inspector can find bugs in minutes that console.log sprinkled through a codebase might not surface for hours.
The debugger Statement
Drop debugger into your code and it behaves like a breakpoint in DevTools:
function processData(data) {
debugger; // execution pauses here when DevTools is open
return data.map(item => transform(item));
}
When DevTools is closed, debugger statements are ignored. When open, execution pauses and you get the full debugger context: call stack, local variables, and the ability to step through code line by line.
Console Methods Beyond console.log
// Formatted output
console.log('%cError', 'color: red; font-weight: bold', errorMessage);
// Tables — perfect for arrays of objects
console.table(users); // renders a sortable table in DevTools
// Groups — collapse related logs
console.group('User processing');
console.log('Fetching user', userId);
console.log('Transforming response', data);
console.groupEnd();
// Timing
console.time('fetchAndProcess');
await fetchData();
processData();
console.timeEnd('fetchAndProcess'); // logs: "fetchAndProcess: 234ms"
// Assert — only logs if condition is false
console.assert(array.length > 0, 'Expected non-empty array', array);
// Stack trace at any point
console.trace('how did we get here');
// Count how many times a point is reached
console.count('login attempt');
console.countReset('login attempt');
Breakpoints in Firefox DevTools
Firefox DevTools has several types of breakpoints:
Line breakpoints: Click the line number in the Debugger panel. Execution pauses when that line runs.
Conditional breakpoints: Right-click a line number → Add Conditional Breakpoint. Pauses only when a condition is true:
// Only break when id is 42
id === 42
Exception breakpoints: In the Debugger, click the pause-on-exceptions button. Pauses whenever an exception is thrown — even if it's caught.
Event listener breakpoints: In the Debugger sidebar → Event Listener Breakpoints. Pause on click, XHR, timer, etc. without finding the handler code manually.
DOM mutation breakpoints: In the Inspector, right-click a DOM element → Break On → Subtree Modifications. Pauses when that element's children change — useful for tracking unexpected DOM updates.
Stepping Through Code
When paused at a breakpoint:
- Step Over (F10): Execute the current line, pause at the next
- Step Into (F11): If the current line calls a function, step into it
- Step Out (Shift+F11): Execute the rest of the current function, pause at the caller
- Resume (F8): Continue execution until the next breakpoint
The Call Stack panel shows how you got to the current line. Click any frame to see its local variables.
The Scopes panel shows all variables in scope — local, closure, and global.
Network Debugging
The Network panel is essential for API issues:
- Filter by XHR/Fetch to see only API calls
- Click a request to see headers, payload, and response
- Check the Timing tab for where time is spent (DNS, connection, waiting, download)
- Right-click a request → Copy as cURL to replay it with different data
For CORS issues, Firefox shows exactly which header is missing or wrong in the Response section.
Blocking specific requests: Right-click → Block URL. Useful for testing error states.
The Performance Profiler
For performance bugs (sluggish interactions, janky scrolling):
- Open DevTools → Performance
- Click Record
- Interact with the slow part
- Stop recording
The flame chart shows the call stack over time. Wide bars = functions running long. Look for:
- Long tasks (> 50ms): visible as orange markers in the timeline
- Functions that appear repeatedly in the call stack
- Layout/reflow events triggered during animation
Click any function bar to see its exact duration and navigate to the source.
Tip: Use the Throttle dropdown (bottom of the performance panel) to simulate a 4x or 6x CPU slowdown — what your app feels like on a mid-range phone.
Memory Debugging
For memory leaks (page gets slower over time):
- DevTools → Memory → Heap Snapshot
- Take a snapshot
- Use the page (do the action you think leaks)
- Take another snapshot
- Select "Comparison" view — shows what was allocated between snapshots
Common sources of memory leaks:
- Event listeners not removed when components unmount
-
setIntervalnever cleared - Global variables accumulating data
- Closures holding references to large objects
// Leak: event listener added but never removed
function init() {
document.addEventListener('scroll', updateState);
}
// Fix: remove when done
function cleanup() {
document.removeEventListener('scroll', updateState);
}
Source Maps
If your code is minified or bundled, source maps let the debugger show the original source:
// vite.config.js
export default {
build: {
sourcemap: true // generate source maps for production
}
}
In Firefox DevTools, enable "Show original sources" to see your TypeScript/JSX source instead of the compiled bundle.
Debugging Async Code
Async debugging is trickier. Firefox DevTools handles it with async stack traces — the call stack shows frames across async boundaries:
async function loadUser(id) {
const data = await fetch(`/api/users/${id}`); // set breakpoint here
return data.json();
}
When paused at the fetch, the call stack shows the full async chain: which function called loadUser, and where it was awaited.
Promise rejections: Enable "Pause on uncaught exceptions" — it catches rejected promises that aren't try/catch-ed.
Debugging Firefox Extensions
For Firefox extension debugging:
- Go to
about:debugging→ This Firefox - Click "Inspect" next to your extension
- A dedicated DevTools instance opens for your extension's background script
You can set breakpoints in background scripts, inspect extension storage, and debug the popup — all with full DevTools access.
Quick Debugging Checklist
When something is broken:
- Check the Console for errors (red messages are thrown exceptions)
- Check Network for failed requests (red status codes)
- Add
debuggerbefore the problematic line - Step through and watch variables in the Scopes panel
- Check the Call Stack to understand how you got there
- Use conditional breakpoints if the bug only happens in specific cases
The debugger statement + conditional breakpoints + async stack traces cover most bugs you'll encounter. The performance profiler handles the rest.
Top comments (0)