Detailed guides for debugging tools across languages and environments.
Inspect and modify the DOM and CSS in real-time.
Key Operations:
├─ Right-click element → Inspect (jump to element in DOM tree)
├─ Ctrl+Shift+C: Pick element mode (click any element to inspect)
├─ Edit HTML: Double-click tag name, attribute, or text content
├─ Force element state: Right-click → Force state → :hover, :active, :focus
├─ Break on DOM changes: Right-click element → Break on → subtree/attribute/removal
└─ Computed tab: See final computed CSS values and which rule wins
CSS debugging workflow:
1. Inspect the element
2. Check Computed tab for the actual value
3. Look for strikethrough rules (overridden)
4. Check for specificity conflicts
5. Toggle rules on/off with checkboxes
6. Use the color picker, shadow editor, easing editor for visual tweaking
7. Changes persist until page reload (or use Overrides for persistence)
Event listener debugging:
Elements panel → Event Listeners tab
├─ Shows all listeners attached to selected element
├─ Check "Ancestors" to see inherited/delegated listeners
├─ Click filename link to jump to handler source code
├─ "Remove" button to detach a listener for testing
└─ Framework listeners: check "Framework listeners" to unwrap React/Vue
Accessibility inspection:
Elements panel → Accessibility tab
├─ ARIA role and computed properties
├─ Accessible name and description
├─ Keyboard focusability
└─ Color contrast ratio
Essential console methods beyond console.log:
// Structured data display
console.table(arrayOfObjects); // Tabular format with sorting
console.table(arrayOfObjects, ['name','id']); // Show only specific columns
// Grouping related logs
console.group('Processing order #123'); // Collapsible group (open)
console.groupCollapsed('Details'); // Collapsible group (closed)
console.log('item count:', items.length);
console.groupEnd();
console.groupEnd();
// Timing operations
console.time('fetchUsers');
await fetchUsers();
console.timeEnd('fetchUsers'); // "fetchUsers: 142.3ms"
// Stack traces
console.trace('How did we get here?'); // Prints call stack
// Assertions (only logs on failure)
console.assert(user.age > 0, 'Invalid age:', user.age);
// Counting occurrences
console.count('render'); // "render: 1", "render: 2", etc.
console.countReset('render');
// Styled output
console.log('%cIMPORTANT', 'color: red; font-size: 20px; font-weight: bold');
// Object with label
console.dir(domElement, { depth: 3 }); // Expandable object tree
Console utilities (only available in DevTools console, not in code):
$0 // Currently selected element in Elements panel
$_ // Result of last expression
$('selector') // document.querySelector shorthand
$$('selector') // document.querySelectorAll as array
$x('//xpath') // XPath query
copy(object) // Copy object as JSON to clipboard
clear() // Clear the console
// Monitor function calls
monitor(functionName) // Log every call with arguments
unmonitor(functionName)
// Monitor events on an element
monitorEvents($0, 'click') // Log all click events on selected element
monitorEvents(window, 'resize')
unmonitorEvents($0, 'click')
// Get event listeners for an element
getEventListeners($0)
// Query objects by constructor
queryObjects(Promise) // Find all Promise instances in heap
Key Features:
├─ Filter by type: XHR, JS, CSS, Img, Media, Font, Doc, WS, Manifest
├─ Filter by text: URL contains, status code, method
├─ Throttling: Simulate slow connections (Slow 3G, Offline, Custom)
├─ Request blocking: Right-click → Block request URL/domain
├─ Replay request: Right-click → Replay XHR (re-sends identical request)
├─ Copy: Right-click → Copy as cURL/fetch/Node.js
└─ HAR export: Right-click → Save all as HAR with content
Debugging API calls:
1. Open Network panel before reproducing the issue
2. Filter by XHR/Fetch to see only API calls
3. Click the failing request:
- Headers tab: check request method, URL, headers (Authorization, Content-Type)
- Payload tab: inspect request body
- Preview tab: formatted response
- Response tab: raw response
- Timing tab: DNS lookup, TLS, TTFB, content download breakdown
4. Check for CORS errors:
- Look for preflight OPTIONS request
- Check Access-Control-Allow-Origin in response
- Console will show CORS error message
5. Waterfall: hover over bars to see timing breakdown
# Recording a performance trace:
1. Open Performance panel
2. Click Record (or Ctrl+E)
3. Perform the action to profile
4. Click Stop
5. Analyze the flame chart
# Key sections:
├─ Summary: pie chart of time spent (Scripting, Rendering, Painting, Idle)
├─ Main: flame chart showing call stack over time
│ ├─ Wide bars = long-running functions
│ ├─ Red corner = long task (>50ms blocks main thread)
│ └─ Click any bar to see source location and timing
├─ Network: requests timeline correlated with execution
├─ Frames: frame rate and dropped frames
└─ Timings: user timing marks and measures
Web Vitals debugging:
// Measure Core Web Vitals in code
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.startTime.toFixed(0)}ms`);
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
// Layout shift debugging
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
console.log('CLS:', entry.value, entry.sources);
}
}
}).observe({ type: 'layout-shift', buffered: true });
Heap snapshot workflow:
Three-Snapshot Technique (finding memory leaks):
1. Take snapshot 1 (baseline)
2. Perform the action suspected of leaking
3. Take snapshot 2
4. Perform the action again
5. Take snapshot 3
6. Select snapshot 3 → "Objects allocated between snapshot 1 and 2"
7. Look for objects that should have been GC'd but weren't
Key views:
├─ Summary: objects grouped by constructor, sorted by retained size
├─ Comparison: diff between two snapshots (delta of object counts)
├─ Containment: object hierarchy from GC roots
└─ Statistics: pie chart of memory by type
Common memory leak patterns:
// Detached DOM nodes (common in SPAs)
// Symptom: "Detached" elements in heap snapshot
// Cause: JS reference to removed DOM node
let cache = [];
document.getElementById('btn').addEventListener('click', () => {
const el = document.createElement('div');
document.body.appendChild(el);
cache.push(el); // ← Reference keeps element alive even after removal
document.body.removeChild(el); // ← Element is "detached" but not GC'd
});
// Fix: remove from cache when element is removed, or use WeakRef
Breakpoint types:
Line breakpoint: Click line number gutter
Conditional: Right-click line → "Add conditional breakpoint"
Expression: user.id === 'problem-user'
Logpoint: Right-click line → "Add logpoint"
Expression: 'User:', user.name, 'at', new Date()
DOM breakpoint: Elements panel → right-click element → Break on
XHR/fetch: Sources → XHR/fetch Breakpoints → add URL substring
Event listener: Sources → Event Listener Breakpoints → check events
(mouse, keyboard, timer, animation, etc.)
Exception: Sources → pause on caught/uncaught exceptions toggle
Source maps:
Enable source maps: Settings → Sources → Enable JavaScript/CSS source maps
├─ Maps minified/transpiled code back to original source
├─ Works with TypeScript, Babel, Webpack, etc.
├─ Breakpoints set on original source, not compiled output
└─ Stack traces show original file names and line numbers
Debugging source map issues:
1. Check for //# sourceMappingURL= comment at end of JS file
2. Verify the .map file is accessible (Network panel, check 404)
3. Check source map is valid: JSON.parse(mapContent)
# Start with debugger (pauses at first line)
node --inspect-brk app.js
# Start with debugger (does not pause)
node --inspect app.js
# Custom port
node --inspect=0.0.0.0:9230 app.js
# Then open chrome://inspect in Chrome and click "inspect"
# Install globally
npm install -g ndb
# Debug a script
ndb node app.js
# Debug tests
ndb npm test
# Features over standard DevTools:
# - Child process debugging (workers, clusters)
# - Blackboxing of node_modules by default
# - Edit and run code from within debugger
# - Detect common async issues
# Install
npm install -g clinic
# Detect common performance issues
clinic doctor -- node app.js
# Generates HTML report with recommendations
# Profile async operations (event loop delays)
clinic bubbleprof -- node app.js
# Shows async operation flow diagram
# CPU flame graph
clinic flame -- node app.js
# Interactive flame chart in browser
# Each tool outputs a .clinic/ directory with HTML report
# Open the generated .html file in a browser
# Install
npm install -g 0x
# Generate flame graph
0x app.js
# With specific arguments
0x -- node --max-old-space-size=4096 app.js
# Output: flamegraph.html
# Wider bars = more time spent in that function
# Search for your code's function names to find hot paths
# Trace warnings with stack traces
node --trace-warnings app.js
# Trace deprecations
node --trace-deprecation app.js
# Enable debug logging for specific modules
NODE_DEBUG=http,net node app.js
NODE_DEBUG=stream,fs node app.js
# Heap snapshot on OOM
node --heapsnapshot-near-heap-limit=3 app.js
# Generate diagnostic report
node --report-on-fatalerror app.js
# Or in code: process.report.writeReport()
# Trace garbage collection
node --trace-gc app.js
# Launch pdb
python -m pdb script.py
# Or insert breakpoint in code (Python 3.7+)
breakpoint()
# Essential commands:
n (next) Step over (execute current line, stop at next)
s (step) Step into (enter function call)
c (continue) Run until next breakpoint
r (return) Run until current function returns
p expr Print expression value
pp expr Pretty-print expression
l (list) Show source around current line
ll (longlist) Show full source of current function
w (where) Print stack trace (alias: bt)
u (up) Move up one frame in stack
d (down) Move down one frame in stack
b 42 Set breakpoint at line 42
b module.py:42 Set breakpoint in specific file
b func Set breakpoint at function entry
b 42, x > 10 Conditional breakpoint
cl 1 Clear breakpoint number 1
cl Clear all breakpoints
a (args) Print arguments of current function
display expr Display expression value at each stop
undisplay expr Stop displaying
commands 1 Set commands to run when breakpoint 1 is hit
p x
p y
c
end
interact Start interactive Python shell with current scope
# In the application code
import debugpy
debugpy.listen(("0.0.0.0", 5678))
print("Waiting for debugger to attach...")
debugpy.wait_for_client()
debugpy.breakpoint()
// VS Code launch.json
{
"name": "Attach to Remote",
"type": "debugpy",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 }
}
# Install
pip install py-spy
# Profile a running process (no code changes needed)
py-spy top --pid PID
# Generate flame graph
py-spy record -o profile.svg --pid PID
# Profile a command
py-spy record -o profile.svg -- python myapp.py
# Dump all thread stacks (instant, non-invasive)
py-spy dump --pid PID
# Install
pip install memray
# Profile memory usage
memray run script.py
# Generate flame graph of allocations
memray flamegraph output.bin -o memory.html
# Show summary
memray summary output.bin
# Show top allocators
memray stats output.bin
# Live TUI during execution
memray run --live script.py
import tracemalloc
tracemalloc.start()
# ... run suspect code ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 memory allocations ]")
for stat in top_stats[:10]:
print(stat)
# Compare two snapshots to find leaks
snapshot1 = tracemalloc.take_snapshot()
# ... run more code ...
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
for stat in top_stats[:10]:
print(stat)
# Debug a program
dlv debug ./cmd/server
# Debug a test
dlv test ./pkg/handler
# Attach to running process
dlv attach PID
# Connect remotely
dlv debug --headless --listen=:2345 --api-version=2 ./cmd/server
# Then in another terminal:
dlv connect localhost:2345
# Breakpoints
break main.go:42 Set breakpoint at file:line
break mypackage.MyFunction Set breakpoint at function
cond 1 x > 100 Make breakpoint 1 conditional
on 1 print x Execute command when breakpoint 1 hits
clear 1 Remove breakpoint 1
clearall Remove all breakpoints
# Execution
continue (c) Run to next breakpoint
next (n) Step over
step (s) Step into
stepout (so) Step out of current function
restart (r) Restart program
# Inspection
print (p) variable Print variable value
display -a variable Show variable at every stop
set variable = value Modify variable value
locals Show all local variables
args Show function arguments
whatis variable Show type of variable
# Stack
stack (bt) Print stack trace
frame N Switch to frame N
up Move up one frame
down Move down one frame
# Goroutines
goroutines List all goroutines
goroutine N Switch to goroutine N
goroutines -t List goroutines with stack traces
goroutine N bt Stack trace for goroutine N
# Run tests with race detector
go test -race ./...
# Run program with race detector
go run -race ./cmd/server
# Build with race detector
go build -race -o server ./cmd/server
# Example output:
# ==================
# WARNING: DATA RACE
# Write at 0x00c0000b4010 by goroutine 7:
# main.increment()
# main.go:15 +0x38
# Previous read at 0x00c0000b4010 by goroutine 6:
# main.printCount()
# main.go:20 +0x30
# ==================
// Add to your application
import _ "net/http/pprof"
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// ... rest of application
}
# CPU profile (30 seconds)
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Heap profile
go tool pprof http://localhost:6060/debug/pprof/heap
# Goroutine profile (find leaks/deadlocks)
go tool pprof http://localhost:6060/debug/pprof/goroutine
# Block profile (find contention)
go tool pprof http://localhost:6060/debug/pprof/block
# Inside pprof interactive mode:
(pprof) top # Show top consumers
(pprof) top -cum # Top by cumulative time
(pprof) list funcName # Show annotated source
(pprof) web # Open flame graph in browser
(pprof) svg > out.svg # Export as SVG
# Build with debug info (default for dev profile)
cargo build
# Debug with GDB (Linux)
rust-gdb target/debug/myapp
# Debug with LLDB (macOS)
rust-lldb target/debug/myapp
# Inside GDB:
(gdb) break main # Breakpoint at main
(gdb) break src/lib.rs:42 # Breakpoint at file:line
(gdb) run # Start program
(gdb) next # Step over
(gdb) step # Step into
(gdb) print variable # Print value (Rust-aware pretty printing)
(gdb) bt # Backtrace
(gdb) info threads # List threads
(gdb) thread 2 # Switch to thread 2
# Short backtrace (usually sufficient)
RUST_BACKTRACE=1 cargo run
# Full backtrace (all frames including stdlib)
RUST_BACKTRACE=full cargo run
# Combine with log levels
RUST_LOG=debug RUST_BACKTRACE=1 cargo run
# Install miri
rustup component add miri
# Run tests under miri (detects UB, use-after-free, data races)
cargo miri test
# Run a specific binary
cargo miri run
# Miri detects:
# - Use of uninitialized memory
# - Accessing memory out of bounds
# - Use-after-free
# - Invalid use of unsafe/raw pointers
# - Data races (with -Zmiri-disable-isolation)
# - Memory leaks (with -Zmiri-leak-check)
# Install
cargo install flamegraph
# Generate flame graph (needs perf on Linux, dtrace on macOS)
cargo flamegraph
# For a specific binary
cargo flamegraph --bin myapp
# For tests
cargo flamegraph --test my_test
# Output: flamegraph.svg (open in browser)
-- Explain query execution plan
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
-- Verbose explain with all details
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 10;
-- Key things to look for:
-- Seq Scan (full table scan - usually bad on large tables)
-- Nested Loop (can be slow with large datasets)
-- Sort (in-memory vs disk sort)
-- actual time vs estimated time (large difference = stale statistics)
-- Check for slow queries (enable slow query log)
ALTER SYSTEM SET log_min_duration_statement = 100; -- log queries > 100ms
SELECT pg_reload_conf();
-- View currently running queries
SELECT pid, now() - pg_stat_activity.query_start AS duration,
query, state, wait_event_type, wait_event
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY duration DESC;
-- Kill a stuck query
SELECT pg_cancel_backend(PID); -- graceful
SELECT pg_terminate_backend(PID); -- force
-- Check table statistics
SELECT relname, n_live_tup, n_dead_tup,
last_vacuum, last_autovacuum, last_analyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
-- Check index usage
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan ASC; -- low scan count = possibly unused index
-- Lock analysis
SELECT blocked_locks.pid AS blocked_pid,
blocked_activity.usename AS blocked_user,
blocking_locks.pid AS blocking_pid,
blocking_activity.usename AS blocking_user,
blocked_activity.query AS blocked_query,
blocking_activity.query AS blocking_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity
ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.relation = blocked_locks.relation
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity
ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;
-- Connection pool debugging
SELECT count(*), state FROM pg_stat_activity GROUP BY state;
SHOW max_connections;
-- Enable (requires extension)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Top queries by total time
SELECT query, calls, total_exec_time, mean_exec_time,
rows, shared_blks_hit, shared_blks_read
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
-- Top queries by calls (hot queries)
SELECT query, calls, mean_exec_time
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 20;
-- Reset statistics
SELECT pg_stat_statements_reset();
# Show full request/response headers and TLS handshake
curl -v https://api.example.com/endpoint
# Include timing breakdown
curl -w "\n\nDNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
-o /dev/null -s https://api.example.com/endpoint
# Follow redirects and show each hop
curl -vL https://example.com/old-path
# Send with specific headers
curl -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" \
-d '{"key":"value"}' https://api.example.com/endpoint
# Save response headers
curl -D headers.txt https://api.example.com/endpoint
# Start mitmproxy (TUI)
mitmproxy -p 8080
# Start as transparent proxy
mitmproxy --mode transparent
# Filter specific hosts
mitmproxy --intercept "~d api.example.com"
# Record and replay
mitmdump -w traffic.flow # Record
mitmdump -r traffic.flow -p 8080 # Replay
# Key bindings in mitmproxy TUI:
# f: set filter expression
# i: set intercept filter
# e: edit intercepted request/response
# r: replay request
# z: clear all flows
# ?: help
# Capture HTTP traffic on port 80
sudo tcpdump -i any port 80 -A
# Capture and save to file
sudo tcpdump -i any -w capture.pcap
# Filter by host
sudo tcpdump -i any host api.example.com
# Filter by host and port
sudo tcpdump -i any host 10.0.0.5 and port 5432
# Read saved capture
tcpdump -r capture.pcap
# Show packet contents in hex and ASCII
sudo tcpdump -XX -i any port 443
# View container logs
docker logs CONTAINER --tail 100 -f # Last 100 lines, follow
docker logs CONTAINER --since 5m # Last 5 minutes
docker logs CONTAINER 2>&1 | rg "ERROR" # Filter for errors
# Execute command in running container
docker exec -it CONTAINER /bin/sh # Shell into container
docker exec CONTAINER cat /etc/hosts # Run single command
docker exec CONTAINER env # Check environment
# Inspect container configuration
docker inspect CONTAINER # Full JSON config
docker inspect --format '{{.State.Status}}' CONTAINER
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' CONTAINER
# View resource usage
docker stats CONTAINER # Live CPU, memory, I/O
docker top CONTAINER # Running processes
# Check health status
docker inspect --format '{{.State.Health.Status}}' CONTAINER
docker inspect --format '{{json .State.Health}}' CONTAINER | jq .
# Copy files from container for inspection
docker cp CONTAINER:/app/logs/error.log ./error.log
# View filesystem changes
docker diff CONTAINER # Shows A(added), C(changed), D(deleted)
# Container exited? Check the exit code and logs
docker ps -a --filter "status=exited"
docker logs CONTAINER
# Create image from stopped container for inspection
docker commit CONTAINER debug-image
docker run -it debug-image /bin/sh
# Override entrypoint to debug startup issues
docker run -it --entrypoint /bin/sh IMAGE
# Check what the container was doing
docker inspect CONTAINER --format '{{.State.Error}}'
docker inspect CONTAINER --format '{{.State.OOMKilled}}'
docker inspect CONTAINER --format '{{.State.ExitCode}}'
# Exit codes: 0=success, 1=app error, 137=OOM/SIGKILL, 139=segfault, 143=SIGTERM
# Check container networking
docker network ls
docker network inspect bridge
docker exec CONTAINER ping other-container
docker exec CONTAINER nslookup other-service
docker exec CONTAINER curl -v http://other-service:8080/health
# Debug DNS resolution
docker exec CONTAINER cat /etc/resolv.conf
# Check port mapping
docker port CONTAINER
# Run container with SYS_PTRACE capability
docker run --cap-add SYS_PTRACE IMAGE
# strace inside container
docker exec CONTAINER strace -p 1 -f -e trace=network
# nsenter from host (if strace not in container)
PID=$(docker inspect --format '{{.State.Pid}}' CONTAINER)
nsenter -t $PID -n tcpdump -i any -A