Debugging JavaScript: Chrome DevTools, Console & Common Patterns
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 HTMLChrome 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 requestsStepping 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 |
|---|---|---|
| Resume | F8 | Continue until next breakpoint |
| Step Over | F10 | Execute current line, skip into functions |
| Step Into | F11 | Enter the function call on current line |
| Step Out | Shift+F11 | Run 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 collapsibleWhen 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" laneFor 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 <= 1) return 1
return n * factorial(n - 1)
}
// SyntaxError: Unexpected token
// Cause: Malformed JSON
const data = JSON.parse("{'name': 'Alice'}") // Single quotes!
// Fix: Use double quotes in JSON
const data = JSON.parse('{"name": "Alice"}')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 availableDebug 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.
Open JS BeautifierRelated Articles
Web Performance Optimization
Core Web Vitals, loading speed, and performance budgets.
TypeScript Generics Guide
Catch type errors at compile time instead of runtime debugging.
Regex Cheat Sheet
Patterns for validation logic you can test in the Regex Tester.
How to Format JSON
Format and debug API responses with syntax validation.