Debugging JavaScript: Chrome DevTools, Console & Common Patterns
const faqSchema = '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: [
'@type': 'Question', name: 'What is the best way to debug JavaScript?', acceptedAnswer: '@type': 'Answer', text: 'Use Chrome DevTools breakpoints instead of console.log for anything beyond simple value checks. Set breakpoints in the Sources panel to pause execution, inspect variables in scope, step through code line by line, and examine the call stack. For async code, use async stack traces (enabled by default in Chrome). For DOM issues, use DOM breakpoints that pause when elements are modified. For network-related bugs, use the Network panel to inspect request/response payloads and timing.', , ,
'@type': 'Question', name: 'How do I debug async/await code in JavaScript?', acceptedAnswer: '@type': 'Answer', text: 'Chrome DevTools shows async call stacks by default, revealing the full chain of async calls that led to the current execution point. Set breakpoints inside async functions and use the Call Stack panel to trace back through await points. Use try/catch blocks with specific error types to handle different failure modes. For unhandled promise rejections, add a global handler: window.addEventListener("unhandledrejection", event => console.error(event.reason)). The console.trace() method also works inside async functions to print the current stack.', , ,
'@type': 'Question', name: 'How do I find memory leaks in JavaScript?', acceptedAnswer: '@type': 'Answer', text: 'Use Chrome DevTools Memory panel to take heap snapshots before and after the suspected leak, then compare them to find objects that should have been garbage collected but were not. Common causes include: event listeners not removed when components unmount, closures holding references to large objects, global variables accumulating data, detached DOM nodes still referenced in JavaScript, and setInterval/setTimeout callbacks not cleared. The Performance Monitor (Ctrl+Shift+P → "Show Performance Monitor") shows real-time JS heap size to confirm if memory grows continuously.', , ,
'@type': 'Question', name: 'What are the most useful console methods besides console.log?', acceptedAnswer: '@type': 'Answer', text: 'console.table() displays arrays/objects as sortable tables. console.group()/groupEnd() creates collapsible log groups. console.time()/timeEnd() measures execution duration. console.assert(condition, message) logs only when the condition is false. console.trace() prints the call stack. console.dir(element) shows DOM element properties instead of HTML. console.count(label) counts how many times a code path executes. console.warn() and console.error() add visual distinction and stack traces to help differentiate log severity.', , , ],
return (
Home / Blog / Debugging JavaScript Guide
# Debugging JavaScript: Chrome DevTools, Console & Common Patterns
JavaScript March 8, 2026 14 min read
Beyond console.log: The Console API
Most developers only use console.log(), but the Console API has powerful methods that make debugging faster and outputs easier to read. These methods work in all modern browsers and Node.js.
`// console.table() — Display arrays/objects as sortable tables const users = [ name: 'Alice', role: 'admin', active: true , name: 'Bob', role: 'editor', active: false , name: 'Charlie', role: 'viewer', active: true , ] console.table(users) // Beautiful table in DevTools console.table(users, ['name', 'role']) // Only specific columns
// console.group() — Collapsible log groups console.group('User Authentication') console.log('Token valid:', true) console.log('Roles:', ['admin', 'editor']) console.groupEnd()
// console.time() — Measure execution time console.time('API Call') await fetch('/api/data') console.timeEnd('API Call') // "API Call: 142.3ms"
// console.assert() — Log only when condition is FALSE console.assert(user.age >= 18, 'User is underage:', user)
// console.trace() — Print the full call stack function processOrder(order) console.trace('Processing order') // Shows who called this
// console.count() — Count executions function handleClick() console.count('button clicked') // "button clicked: 1", "2", "3"
// console.dir() — Inspect DOM elements as objects console.dir(document.querySelector('#app')) // Properties, not HTML`
Chrome DevTools Breakpoints
Breakpoints pause code execution at a specific line, letting you inspect every variable in scope, the call stack, and the state of the DOM. They are far more powerful than console.log because you can explore the entire application state interactively.
`// 1. Line breakpoints: Click the line number in Sources panel
// 2. Conditional breakpoints: Right-click line → "Add conditional breakpoint" // Only pauses when condition is true (e.g., user.id === 42)
// 3. Logpoints: Right-click → "Add logpoint" // Logs a message WITHOUT pausing execution (like console.log but no code change) // Example logpoint: "User loaded: user.name, role: user.role"
// 4. debugger statement: Programmatic breakpoint function calculateTotal(items) const subtotal = items.reduce((sum, item) => sum + item.price, 0) debugger // Pauses here when DevTools is open const tax = subtotal * 0.08 return subtotal + tax
// 5. DOM breakpoints: Right-click element in Elements panel → // "Break on" → subtree modifications / attribute modifications / node removal // Pauses when JavaScript modifies this DOM element
// 6. Event listener breakpoints: Sources panel → Event Listener Breakpoints // Check "Mouse → click" to pause on ANY click handler
// 7. XHR/Fetch breakpoints: Sources panel → XHR/Fetch Breakpoints // Add URL pattern like "/api/users" to pause on matching requests`
Stepping Through Code
Once paused at a breakpoint, the stepping controls let you execute code one line at a time. Understanding each step action is crucial for effective debugging.
Action Shortcut Behavior
ResumeF8Continue until next breakpoint Step OverF10Execute current line, skip into functions Step IntoF11Enter the function call on current line Step OutShift+F11Run to end of current function, pause in caller Restart Frame--Re-run the current function from the beginning
While paused, hover over any variable to see its current value. Use the Scope panel to see all local, closure, and global variables. The Watch panel lets you track specific expressions across step operations.
Network Panel: Debugging API Calls
The Network panel records every HTTP request the page makes, showing timing, headers, request/response bodies, and status codes. It is essential for debugging API integrations, slow page loads, and authentication issues.
`// Debugging tips for the Network panel:
// 1. Filter by type: Click "Fetch/XHR" to see only API calls // 2. Search: Use the search bar to find requests by URL or content // 3. Preserve log: Check "Preserve log" to keep requests across navigations
// 4. Copy as cURL: Right-click any request → "Copy as cURL" // Paste into terminal to reproduce the exact request: curl 'https://api.example.com/users' \ -H 'Authorization: Bearer eyJ...' \ -H 'Content-Type: application/json'
// 5. Replay request: Right-click → "Replay XHR" // Re-sends the exact same request without reloading the page
// 6. Throttling: Simulate slow networks // Choose "Slow 3G" or "Offline" to test loading states
// 7. Block requests: Right-click → "Block request URL" // Test how your app handles failed API calls
// 8. Response preview: Click request → "Preview" tab // JSON responses are formatted and collapsible`
When debugging API responses, copy the JSON from the Network panel into our JSON Formatter for a better view with syntax highlighting, or use the Diff Checker to compare expected vs actual responses.
Debugging Async Code
Asynchronous JavaScript (Promises, async/await, callbacks) introduces debugging challenges because the code does not execute sequentially. Chrome DevTools provides async stack traces that show the full chain of async calls.
`// Common async debugging patterns
// 1. Catch unhandled rejections globally window.addEventListener('unhandledrejection', (event) => console.error('Unhandled promise rejection:', event.reason) console.error('Promise:', event.promise) )
// 2. Always use try/catch with async/await async function fetchUserData(userId) try const response = await fetch(`/api/users/$userId`) if (!response.ok) throw new Error(`HTTP $response.status: $response.statusText`)
return await response.json() catch (error) if (error instanceof TypeError) console.error('Network error (offline or CORS):', error) else console.error('API error:', error.message)
throw error // Re-throw for caller to handle
// 3. Debug race conditions with labeled logs async function loadDashboard() console.time('dashboard-load')
const [users, orders, stats] = await Promise.all([ fetch('/api/users').then(r => console.timeLog('dashboard-load', 'users loaded') return r.json() ), fetch('/api/orders').then(r => console.timeLog('dashboard-load', 'orders loaded') return r.json() ), fetch('/api/stats').then(r => console.timeLog('dashboard-load', 'stats loaded') return r.json() ), ])
console.timeEnd('dashboard-load')
// 4. Promise.allSettled for partial failures const results = await Promise.allSettled([ fetch('/api/critical-data'), fetch('/api/optional-data'), ]) results.forEach((result, i) => if (result.status === 'rejected') console.warn(`Request $i failed:`, result.reason)
)`
Memory Leak Detection
Memory leaks cause web applications to slow down over time and eventually crash. The Chrome DevTools Memory panel helps identify objects that should have been garbage collected but are still referenced.
`// Common memory leak patterns and fixes
// LEAK: Event listeners not removed class Component constructor() // BAD: Listener never removed window.addEventListener('resize', this.handleResize)
// FIX: Remove listener on cleanup destroy() window.removeEventListener('resize', this.handleResize)
// LEAK: setInterval not cleared // BAD: setInterval(() => updateData(), 1000) // FIX: const intervalId = setInterval(() => updateData(), 1000) // On cleanup: clearInterval(intervalId)
// LEAK: Closures holding large objects function createProcessor() const hugeData = new Array(1000000).fill('x') // 1M items return function process(item) // This closure keeps hugeData alive forever! return hugeData.includes(item)
// FIX: Only capture what you need function createProcessor() const hugeData = new Array(1000000).fill('x') const dataSet = new Set(hugeData) // More efficient return function process(item) return dataSet.has(item)
// hugeData is now eligible for GC
// LEAK: Detached DOM nodes const elements = [] function addItem() const el = document.createElement('div') document.body.appendChild(el) elements.push(el) // Array reference prevents GC
function removeItem() const el = elements.pop() el.remove() // Removed from DOM but still in array! `
Performance Profiling
The Performance panel records everything that happens during a time period: JavaScript execution, layout calculations, painting, and network activity. It reveals exactly what is causing janky animations or slow interactions.
`// Performance API: Measure specific operations in code
// Mark points of interest performance.mark('render-start') renderComponent() performance.mark('render-end')
// Measure between marks performance.measure('render-duration', 'render-start', 'render-end')
// Read measurements const measures = performance.getEntriesByName('render-duration') console.log('Render took:', measures[0].duration, 'ms')
// User Timing API for detailed profiling performance.mark('fetch-start') const data = await fetch('/api/heavy-query') performance.mark('fetch-end') performance.mark('process-start') processData(data) performance.mark('process-end')
performance.measure('fetch', 'fetch-start', 'fetch-end') performance.measure('process', 'process-start', 'process-end')
// These measurements appear in DevTools Performance recordings // Look for them in the "Timings" lane`
For comprehensive web performance optimization techniques including Core Web Vitals, see our web performance optimization guide.
Common JavaScript Error Patterns
Knowing the most common JavaScript errors and their causes saves hours of debugging. These patterns cover the majority of runtime errors in production applications.
`// TypeError: Cannot read properties of undefined // Cause: Accessing a property on undefined/null const user = getUser() // Returns undefined console.log(user.name) // TypeError! // Fix: Optional chaining console.log(user?.name)
// TypeError: X is not a function // Cause: Calling a non-function value const config = debug: true config.debug() // TypeError: config.debug is not a function // Fix: Check type before calling if (typeof config.debug === 'function') config.debug()
// ReferenceError: X is not defined // Cause: Using a variable before declaration or misspelled name console.log(userName) // ReferenceError // Fix: Check spelling, ensure imports
// RangeError: Maximum call stack size exceeded // Cause: Infinite recursion function factorial(n) return n * factorial(n - 1) // No base case!
// Fix: Add base case function factorial(n) if (n
When debugging JSON parsing errors, paste the problematic string into our JSON Formatter to instantly see syntax errors highlighted. Test regex patterns used in validation logic to verify they match expected inputs.
Source Maps: Debugging Minified Code
Production JavaScript is minified and bundled, making stack traces unreadable. Source maps connect the minified code back to your original source files, letting you debug production errors as if you were reading your development code.
`// webpack.config.js — Enable source maps module.exports = // Development: Full source maps (slow build, best debugging) devtool: 'source-map',
// Production: Hidden source maps (uploaded to error tracking) devtool: 'hidden-source-map',
// vite.config.ts — Source maps export default build: sourcemap: true, // or 'hidden' for production ,
// Upload source maps to error tracking (Sentry example) // sentry-cli sourcemaps upload --release=1.0.0 ./dist
// The source map file (.js.map) maps positions: // bundle.min.js line 1, col 4523 → src/utils/auth.ts line 42, col 8 // DevTools uses this mapping automatically when source maps are available`
/ CTA Section /
Debug Faster with BytePane Tools
Format API responses with our JSON Formatter, test validation patterns with the Regex Tester, decode JWT tokens from auth headers with the JWT Decoder, and beautify minified code with the JavaScript Beautifier.
/ Related Articles /
Related Articles
[
Web Performance Optimization
Core Web Vitals, loading speed, and performance budgets.
](/blog/web-performance-optimization/) [
TypeScript Generics Guide
Catch type errors at compile time instead of runtime debugging.
](/blog/typescript-generics-guide/) [
Regex Cheat Sheet
Patterns for validation logic you can test in the Regex Tester.
](/blog/regex-cheat-sheet/) [
How to Format JSON
Format and debug API responses with syntax validation.
](/blog/json-formatting-guide/)
)
Originally published on bytepane.com. Visit for free interactive calculators and tools.