Master debugging workflows and tools to identify and fix bugs efficiently
Master debugging workflows and tools to identify and fix bugs efficiently
Effective debugging is one of the most valuable skills a developer can master. While console.log() works, modern debuggers offer far more powerful capabilities.
The Debugging Lifecycle:
Why Use a Debugger Instead of console.log:
1// ❌ Console.log debugging - Limited insight
2function calculateTotal(items) {
3 console.log('items:', items);
4 let total = 0;
5 for (let item of items) {
6 console.log('item:', item);
7 total += item.price * item.quantity;
8 console.log('total so far:', total);
9 }
10 console.log('final total:', total);
11 return total;
12}
13
14// ✅ With debugger - Full insight at any point
15function calculateTotal(items) {
16 let total = 0;
17 for (let item of items) {
18 // Set breakpoint here - inspect all variables at any iteration
19 total += item.price * item.quantity;
20 }
21 return total;
22}Advantages of Real Debuggers:
Common Debugging Scenarios:
The Debugging Mindset:
1// Good debugging approach:
2// 1. Form a hypothesis about what's wrong
3// 2. Design an experiment to test the hypothesis
4// 3. Run the experiment (use debugger to test)
5// 4. Analyze results
6// 5. Refine hypothesis and repeat
7
8// Example: "I think the discount isn't being applied"
9function applyDiscount(price: number, discountPercent: number): number {
10 // Hypothesis: discountPercent might be a whole number instead of decimal
11 // Test: Set breakpoint here and inspect discountPercent value
12 const discountAmount = price * discountPercent; // Bug: should be discountPercent / 100
13 return price - discountAmount;
14}
15
16// After debugging: Found that discountPercent is 10 not 0.1
17// Fix: Divide by 100Essential Debugging Skills:
VS Code has a powerful built-in debugger that works with many languages. Setting it up properly is crucial for effective debugging.
Launch Configuration (launch.json):
1// .vscode/launch.json - Debugging configuration file
2{
3 "version": "0.2.0",
4 "configurations": [
5 {
6 "type": "node",
7 "request": "launch",
8 "name": "Launch Node.js Program",
9 "skipFiles": ["<node_internals>/**"],
10 "program": "${workspaceFolder}/src/index.js",
11 "outFiles": ["${workspaceFolder}/dist/**/*.js"],
12 "console": "integratedTerminal"
13 },
14 {
15 "type": "chrome",
16 "request": "launch",
17 "name": "Launch Chrome",
18 "url": "http://localhost:3000",
19 "webRoot": "${workspaceFolder}/src",
20 "sourceMaps": true
21 },
22 {
23 "type": "node",
24 "request": "attach",
25 "name": "Attach to Process",
26 "port": 9229,
27 "skipFiles": ["<node_internals>/**"]
28 }
29 ],
30 "compounds": [
31 {
32 "name": "Full Stack Debug",
33 "configurations": ["Launch Node.js Program", "Launch Chrome"]
34 }
35 ]
36}Common Configuration Options:
1{
2 "type": "node", // Debugger type: node, chrome, python, etc.
3 "request": "launch", // "launch" starts app, "attach" connects to running process
4 "name": "Debug Server", // Name shown in debug dropdown
5 "program": "src/server.js", // Entry point file
6 "args": ["--port", "3000"], // Command line arguments
7 "env": { // Environment variables
8 "NODE_ENV": "development",
9 "DEBUG": "*"
10 },
11 "envFile": "${workspaceFolder}/.env", // Load env vars from file
12 "cwd": "${workspaceFolder}", // Working directory
13 "runtimeExecutable": "node", // Runtime to use
14 "runtimeArgs": [ // Args passed to runtime
15 "--require", "ts-node/register"
16 ],
17 "sourceMaps": true, // Enable source map support
18 "outFiles": ["dist/**/*.js"], // Location of transpiled files
19 "skipFiles": [ // Files to skip when stepping
20 "<node_internals>/**",
21 "node_modules/**"
22 ],
23 "console": "integratedTerminal", // Where to show output
24 "restart": true, // Restart on code changes
25 "stopOnEntry": false, // Pause on first line
26 "preLaunchTask": "npm: build" // Task to run before debugging
27}Language-Specific Configurations:
1// TypeScript/Node.js with ts-node
2{
3 "type": "node",
4 "request": "launch",
5 "name": "Debug TypeScript",
6 "runtimeArgs": ["-r", "ts-node/register"],
7 "args": ["${workspaceFolder}/src/index.ts"],
8 "sourceMaps": true,
9 "smartStep": true
10}
11
12// Python debugging
13{
14 "type": "python",
15 "request": "launch",
16 "name": "Python: Current File",
17 "program": "${file}",
18 "console": "integratedTerminal",
19 "justMyCode": true
20}
21
22// Next.js debugging (client + server)
23{
24 "type": "node",
25 "request": "launch",
26 "name": "Next.js: debug full stack",
27 "runtimeExecutable": "npm",
28 "runtimeArgs": ["run", "dev"],
29 "port": 9229,
30 "serverReadyAction": {
31 "pattern": "started server on .+, url: (https?://.+)",
32 "uriFormat": "%s",
33 "action": "debugWithChrome"
34 }
35}
36
37// Jest testing
38{
39 "type": "node",
40 "request": "launch",
41 "name": "Jest: Current File",
42 "program": "${workspaceFolder}/node_modules/.bin/jest",
43 "args": ["${fileBasenameNoExtension}", "--config", "jest.config.js"],
44 "console": "integratedTerminal",
45 "internalConsoleOptions": "neverOpen"
46}Multi-Target Debugging (Compounds):
1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "name": "Launch Backend",
6 "type": "node",
7 "request": "launch",
8 "program": "${workspaceFolder}/server/index.js"
9 },
10 {
11 "name": "Launch Frontend",
12 "type": "chrome",
13 "request": "launch",
14 "url": "http://localhost:3000",
15 "webRoot": "${workspaceFolder}/client"
16 }
17 ],
18 "compounds": [
19 {
20 "name": "Full Stack",
21 "configurations": ["Launch Backend", "Launch Frontend"],
22 "stopAll": true
23 }
24 ]
25}Breakpoints are the heart of debugging. Understanding the different types and how to use them effectively is crucial.
Types of Breakpoints:
1. Regular Breakpoints:
1function processOrder(order) {
2 // Click in gutter (left of line numbers) to set breakpoint
3 validateOrder(order); // ← Set breakpoint here
4
5 const total = calculateTotal(order.items);
6 const discount = applyDiscount(total, order.coupon);
7
8 return {
9 total,
10 discount,
11 finalAmount: total - discount
12 };
13}2. Conditional Breakpoints:
1// Right-click breakpoint → Edit Breakpoint → Enter condition
2for (let i = 0; i < users.length; i++) {
3 const user = users[i];
4 // Breakpoint condition: user.role === 'admin'
5 // Only pauses when user is an admin
6 processUser(user);
7}
8
9// Another example
10function handleRequest(req) {
11 // Breakpoint condition: req.method === 'POST' && req.url.includes('/api/users')
12 // Only pauses for POST requests to /api/users
13 const result = processRequest(req);
14 return result;
15}3. Logpoints (Non-breaking Breakpoints):
1// Right-click → Add Logpoint
2// Logs without stopping execution
3function updateUserProfile(userId, data) {
4 // Logpoint: "Updating user {userId} with data {data}"
5 // Outputs to debug console without pausing
6
7 const user = findUser(userId);
8 Object.assign(user, data);
9 saveUser(user);
10
11 // Logpoint: "User {userId} updated successfully"
12 return user;
13}4. Hit Count Breakpoints:
1// Breakpoint condition: hit count > 100
2// Only pauses after 100th hit
3function processItem(item) {
4 // Useful for loops - pause on 100th iteration
5 return transform(item);
6}5. Exception Breakpoints:
1// In VS Code: Check "All Exceptions" or "Uncaught Exceptions"
2function riskyOperation() {
3 try {
4 // Debugger will pause here when exception is thrown
5 const data = JSON.parse(corruptedData);
6 } catch (error) {
7 // Or pause here for caught exceptions
8 console.error(error);
9 }
10}Stepping Commands:
1function outerFunction(x) {
2 console.log('Outer start');
3 const result = innerFunction(x); // ← Breakpoint here
4 console.log('Outer end');
5 return result;
6}
7
8function innerFunction(y) {
9 const doubled = y * 2;
10 return doubled + 10;
11}
12
13// Stepping options when paused at breakpoint:
14// F10 - Step Over: Execute innerFunction() and move to next line (console.log)
15// F11 - Step Into: Enter innerFunction() and pause on first line
16// Shift+F11 - Step Out: Run until current function returns
17// F5 - Continue: Run until next breakpoint
18// Shift+F5 - Stop: Stop debuggingAdvanced Stepping:
1// Smart Step (automatically skip getter/setter)
2class User {
3 private _name: string;
4
5 get name() {
6 return this._name; // Skipped by smart step
7 }
8
9 set name(value: string) {
10 this._name = value; // Skipped by smart step
11 }
12}
13
14// Step Back (time travel debugging in some debuggers)
15function complexCalculation() {
16 let result = 0;
17 result += step1(); // ← Breakpoint
18 result += step2(); // Step over
19 result += step3(); // Oops, want to see step2() again
20 // Step Back to go to previous line
21 return result;
22}Breakpoint Best Practices:
1// ✅ Good: Set breakpoint at start of suspicious code
2function calculateDiscount(price: number, code: string): number {
3 // Start here to see inputs
4 const discountRate = lookupDiscountCode(code);
5 const discountAmount = price * discountRate;
6 return discountAmount;
7}
8
9// ✅ Good: Use conditional breakpoints for specific cases
10items.forEach((item, index) => {
11 // Condition: item.id === 'problematic-id'
12 processItem(item);
13});
14
15// ❌ Bad: Too many breakpoints
16function process() {
17 const a = 1; // breakpoint
18 const b = 2; // breakpoint
19 const c = 3; // breakpoint
20 return a + b + c; // breakpoint
21}
22
23// ✅ Better: One breakpoint + step through
24function process() {
25 const a = 1; // Single breakpoint here
26 const b = 2; // Then step through
27 const c = 3;
28 return a + b + c;
29}Understanding the debugging panels and how to inspect program state is essential for effective debugging.
Variables Panel:
1function processOrder(order) {
2 // When paused here, Variables panel shows:
3
4 // Local variables
5 const items = order.items; // items: Array(3) [{...}, {...}, {...}]
6 let total = 0; // total: 0
7
8 // Hover over variables to see values
9 for (const item of items) {
10 // item: {id: 1, name: "Widget", price: 29.99, quantity: 2}
11 total += item.price * item.quantity;
12 }
13
14 // this context (if in class method)
15 // this: OrderProcessor {config: {...}, logger: {...}}
16
17 // Closure variables
18 const tax = 0.08; // tax: 0.08
19
20 return total;
21}
22
23// Variables panel structure:
24// ▼ Local
25// ▸ order: {items: Array(3), customer: {...}, ...}
26// ▸ items: Array(3)
27// ▸ total: 89.97
28// ▼ Closure
29// ▸ tax: 0.08
30// ▼ Global
31// ▸ window: Window {...}Watch Expressions:
1// Add expressions to watch continuously
2function calculatePrice(items, discount, taxRate) {
3 // Add to Watch panel:
4 // items.length → 5
5 // items[0].price * items[0].quantity → 59.98
6 // discount > 0 → true
7 // taxRate * 100 + '%' → "8%"
8
9 const subtotal = items.reduce((sum, item) =>
10 sum + (item.price * item.quantity), 0);
11
12 // Watch updates as you step:
13 // subtotal → 299.90
14 // subtotal * (1 - discount) → 269.91
15 // subtotal * (1 + taxRate) → 323.89
16
17 const afterDiscount = subtotal * (1 - discount);
18 const total = afterDiscount * (1 + taxRate);
19
20 return total;
21}
22
23// Useful watch expressions:
24// - items.filter(i => i.price > 50) // Find expensive items
25// - Object.keys(config) // See all config keys
26// - JSON.stringify(user, null, 2) // Pretty print object
27// - new Date(timestamp).toISOString() // Convert timestamp
28// - arr.length > 0 ? arr[0] : null // Safe accessCall Stack Navigation:
1// main.js
2function main() {
3 const user = fetchUser(123); // Stack frame 3
4 processUser(user);
5}
6
7// userService.js
8function fetchUser(id) {
9 const data = database.query(id); // Stack frame 2
10 return parseUser(data);
11}
12
13// userParser.js
14function parseUser(data) {
15 const user = JSON.parse(data); // Stack frame 1 (current) ← Paused here
16 return transformUser(user);
17}
18
19// Call Stack panel shows:
20// ▼ Call Stack
21// ▸ parseUser (userParser.js:12) ← Current frame
22// ▸ fetchUser (userService.js:24)
23// ▸ main (main.js:5)
24// ▸ <anonymous> (main.js:50)
25
26// Click any frame to:
27// - See variables in that scope
28// - See the code at that point
29// - Understand how you got hereDebug Console (REPL):
1function processData(data) {
2 const transformed = transform(data); // ← Paused here
3 return transformed;
4}
5
6// In Debug Console, you can:
7// - Evaluate expressions in current scope
8data // See data value
9data.items.length // Check array length
10data.items.map(i => i.name) // Transform and inspect
11
12// - Call functions
13console.log(JSON.stringify(data, null, 2)) // Pretty print
14transform({test: true}) // Test with different input
15
16// - Modify variables (be careful!)
17data.items = data.items.filter(i => i.active) // Change data
18// Now continue execution with modified data
19
20// - Access global objects
21window // In browser
22global // In Node.js
23process.env // Environment variablesInspecting Complex Objects:
1// Deep object inspection
2interface User {
3 id: number;
4 profile: {
5 name: string;
6 email: string;
7 settings: {
8 theme: string;
9 notifications: {
10 email: boolean;
11 push: boolean;
12 };
13 };
14 };
15 orders: Order[];
16}
17
18function processUser(user: User) {
19 // In Variables panel:
20 // ▼ user
21 // ▸ id: 123
22 // ▼ profile
23 // ▸ name: "John Doe"
24 // ▸ email: "[email protected]"
25 // ▼ settings
26 // ▸ theme: "dark"
27 // ▼ notifications
28 // ▸ email: true
29 // ▸ push: false
30 // ▼ orders: Array(5)
31 // ▼ 0: {id: 1, total: 99.99, ...}
32 // ▼ 1: {id: 2, total: 149.99, ...}
33
34 // Right-click → Copy Value
35 // Right-click → Copy as Expression
36 // Right-click → Add to Watch
37}Debugging Asynchronous Code:
1async function fetchUserData(userId) {
2 // Breakpoint here
3 const user = await fetch(`/api/users/${userId}`);
4 // Debugger pauses before AND after await
5
6 const data = await user.json();
7 // Can inspect promises in Variables panel
8 // Promise { <state>: "fulfilled", <value>: {...} }
9
10 return data;
11}
12
13// Call stack shows async frames:
14// ▼ Call Stack
15// ▸ fetchUserData (service.js:15)
16// ▸ async Promise.then
17// ▸ handleRequest (controller.js:42)Different types of applications require different debugging approaches and configurations.
Frontend (React/Vue/Angular) Debugging:
1// Browser debugging with Source Maps
2// Component: UserProfile.tsx
3
4import React, { useState, useEffect } from 'react';
5
6function UserProfile({ userId }) {
7 const [user, setUser] = useState(null);
8 const [loading, setLoading] = useState(true);
9
10 useEffect(() => {
11 // ← Set breakpoint here
12 fetchUser(userId)
13 .then(data => {
14 setUser(data); // ← Or here to inspect data
15 setLoading(false);
16 })
17 .catch(error => {
18 console.error(error); // ← Or here for errors
19 setLoading(false);
20 });
21 }, [userId]);
22
23 // Debug in Chrome DevTools:
24 // 1. Open DevTools (F12)
25 // 2. Go to Sources tab
26 // 3. Find your TypeScript/JSX file (with source maps)
27 // 4. Set breakpoints
28 // 5. Interact with app to trigger breakpoints
29
30 if (loading) return <div>Loading...</div>;
31 return <div>{user?.name}</div>;
32}
33
34// VS Code debugging with Chrome debugger:
35// launch.json
36{
37 "type": "chrome",
38 "request": "launch",
39 "name": "Debug React App",
40 "url": "http://localhost:3000",
41 "webRoot": "${workspaceFolder}/src",
42 "sourceMaps": true,
43 "sourceMapPathOverrides": {
44 "webpack:///src/*": "${webRoot}/*"
45 }
46}Backend (Node.js/Express) Debugging:
1// server.js
2const express = require('express');
3const app = express();
4
5app.get('/api/users/:id', async (req, res) => {
6 // ← Set breakpoint here
7 const userId = req.params.id;
8
9 try {
10 const user = await db.users.findById(userId); // ← Inspect query results
11
12 if (!user) {
13 return res.status(404).json({ error: 'User not found' });
14 }
15
16 res.json(user); // ← See what's being sent
17 } catch (error) {
18 // ← Debug error cases
19 console.error('Error fetching user:', error);
20 res.status(500).json({ error: 'Internal server error' });
21 }
22});
23
24// Start with debugging enabled:
25// node --inspect-brk server.js
26// Or in package.json:
27{
28 "scripts": {
29 "debug": "node --inspect-brk server.js"
30 }
31}
32
33// VS Code launch.json:
34{
35 "type": "node",
36 "request": "launch",
37 "name": "Debug Server",
38 "program": "${workspaceFolder}/server.js",
39 "restart": true,
40 "console": "integratedTerminal"
41}Full-Stack Debugging:
1// Debug client and server simultaneously
2{
3 "version": "0.2.0",
4 "configurations": [
5 {
6 "name": "Server",
7 "type": "node",
8 "request": "launch",
9 "program": "${workspaceFolder}/server/index.js",
10 "envFile": "${workspaceFolder}/.env"
11 },
12 {
13 "name": "Client",
14 "type": "chrome",
15 "request": "launch",
16 "url": "http://localhost:3000",
17 "webRoot": "${workspaceFolder}/client/src"
18 }
19 ],
20 "compounds": [
21 {
22 "name": "Full Stack",
23 "configurations": ["Server", "Client"]
24 }
25 ]
26}API/Integration Debugging:
1// Testing API calls
2async function testUserAPI() {
3 // Use VS Code REST Client extension or similar
4 const response = await fetch('http://localhost:3000/api/users/123', {
5 method: 'GET',
6 headers: {
7 'Authorization': 'Bearer ' + token, // ← Breakpoint to inspect token
8 'Content-Type': 'application/json'
9 }
10 });
11
12 const data = await response.json(); // ← Inspect response
13 console.log(data);
14
15 // Or use .http files:
16 // ### Get User
17 // GET http://localhost:3000/api/users/123
18 // Authorization: Bearer {{token}}
19}Database Query Debugging:
1// Debugging ORM queries (Prisma example)
2async function getUserWithOrders(userId) {
3 // Enable query logging
4 const user = await prisma.user.findUnique({
5 where: { id: userId }, // ← Breakpoint here
6 include: {
7 orders: {
8 include: {
9 items: true
10 }
11 }
12 }
13 });
14
15 // Prisma logs SQL:
16 // prisma:query SELECT ... FROM "User" WHERE "id" = $1
17 // ← Inspect generated SQL
18
19 return user; // ← Inspect results
20}
21
22// Enable query logging:
23const prisma = new PrismaClient({
24 log: ['query', 'info', 'warn', 'error'],
25});Testing/Jest Debugging:
1// userService.test.js
2describe('UserService', () => {
3 it('should fetch user by id', async () => {
4 // ← Set breakpoint in test
5 const mockUser = { id: 1, name: 'Test User' };
6 mockFetch.mockResolvedValue(mockUser);
7
8 const result = await userService.getUser(1); // ← Step into service
9
10 expect(result).toEqual(mockUser); // ← Verify result
11 });
12});
13
14// launch.json for Jest:
15{
16 "type": "node",
17 "request": "launch",
18 "name": "Jest Current File",
19 "program": "${workspaceFolder}/node_modules/.bin/jest",
20 "args": [
21 "${fileBasenameNoExtension}",
22 "--runInBand",
23 "--no-cache"
24 ],
25 "console": "integratedTerminal"
26}WebSocket/Real-time Debugging:
1// Debug WebSocket connections
2const ws = new WebSocket('ws://localhost:8080');
3
4ws.onopen = () => {
5 console.log('Connected'); // ← Breakpoint
6 ws.send(JSON.stringify({ type: 'subscribe', channel: 'users' }));
7};
8
9ws.onmessage = (event) => {
10 const data = JSON.parse(event.data); // ← Breakpoint to inspect messages
11 handleMessage(data);
12};
13
14ws.onerror = (error) => {
15 console.error('WebSocket error:', error); // ← Debug connection issues
16};
17
18// Use Chrome DevTools → Network → WS tab to see WebSocket framesMaster these advanced techniques to debug complex issues and optimize your debugging workflow.
Remote Debugging:
1// Debug Node.js running in Docker container
2// Dockerfile:
3FROM node:18
4WORKDIR /app
5COPY package*.json ./
6RUN npm install
7COPY . .
8# Expose debug port
9EXPOSE 3000 9229
10CMD ["node", "--inspect=0.0.0.0:9229", "server.js"]
11
12// docker-compose.yml:
13services:
14 app:
15 build: .
16 ports:
17 - "3000:3000"
18 - "9229:9229" # Debug port
19 volumes:
20 - .:/app
21 - /app/node_modules
22
23// VS Code launch.json:
24{
25 "type": "node",
26 "request": "attach",
27 "name": "Attach to Docker",
28 "address": "localhost",
29 "port": 9229,
30 "localRoot": "${workspaceFolder}",
31 "remoteRoot": "/app",
32 "restart": true
33}Memory Leak Debugging:
1// Detect memory leaks
2class UserCache {
3 constructor() {
4 this.cache = new Map();
5 this.listeners = [];
6 }
7
8 addUser(user) {
9 this.cache.set(user.id, user);
10
11 // ⚠️ Memory leak: event listener never removed
12 user.on('update', (data) => {
13 this.cache.set(user.id, data);
14 });
15 }
16
17 // ✅ Fix: Remove listeners
18 removeUser(userId) {
19 const user = this.cache.get(userId);
20 if (user) {
21 user.removeAllListeners('update');
22 this.cache.delete(userId);
23 }
24 }
25}
26
27// Debug memory leaks:
28// 1. Take heap snapshot before operation
29// 2. Perform operation (e.g., add 1000 users)
30// 3. Take another heap snapshot
31// 4. Compare snapshots in Chrome DevTools
32// 5. Look for retained objects that should be garbage collected
33
34// Chrome DevTools → Memory → Heap snapshotPerformance Profiling:
1// CPU profiling
2function slowFunction() {
3 // ← Start profiler
4 let result = 0;
5 for (let i = 0; i < 1000000; i++) {
6 result += Math.sqrt(i) * Math.sin(i); // Expensive operation
7 }
8 return result;
9 // ← Stop profiler
10}
11
12// In Chrome DevTools:
13// 1. Performance tab → Record
14// 2. Perform action
15// 3. Stop recording
16// 4. Analyze flame graph to find bottlenecks
17
18// In VS Code with Node.js:
19// node --prof server.js
20// node --prof-process isolate-*.log > processed.txtDebugging Production Issues:
1// Production debugging without debugger access
2
3// 1. Structured logging
4const logger = require('winston');
5
6function processOrder(order) {
7 logger.info('Processing order', {
8 orderId: order.id,
9 userId: order.userId,
10 itemCount: order.items.length,
11 total: order.total
12 });
13
14 try {
15 const result = calculateTotal(order);
16 logger.info('Order processed successfully', {
17 orderId: order.id,
18 result
19 });
20 return result;
21 } catch (error) {
22 logger.error('Order processing failed', {
23 orderId: order.id,
24 error: error.message,
25 stack: error.stack
26 });
27 throw error;
28 }
29}
30
31// 2. Error tracking (Sentry, Bugsnag, etc.)
32Sentry.captureException(error, {
33 tags: {
34 component: 'order-processor',
35 environment: process.env.NODE_ENV
36 },
37 extra: {
38 orderId: order.id,
39 userId: order.userId
40 }
41});
42
43// 3. Feature flags for debugging
44if (featureFlags.enableDebugLogging) {
45 console.log('Debug info:', data);
46}Debugging Race Conditions:
1// Race condition example
2let balance = 1000;
3
4async function withdraw(amount) {
5 // Multiple calls can read the same balance
6 const currentBalance = balance; // ← Breakpoint
7
8 // Simulate async operation
9 await new Promise(resolve => setTimeout(resolve, 100));
10
11 // Race condition: another withdraw might have happened
12 if (currentBalance >= amount) {
13 balance = currentBalance - amount; // ← May go negative!
14 }
15}
16
17// Two simultaneous withdrawals:
18Promise.all([
19 withdraw(800), // Reads balance: 1000
20 withdraw(800) // Reads balance: 1000
21]);
22// Result: balance = -600 (should reject one!)
23
24// ✅ Fix with proper locking:
25const mutex = new Mutex();
26
27async function withdrawSafe(amount) {
28 const release = await mutex.acquire();
29 try {
30 if (balance >= amount) {
31 balance -= amount;
32 }
33 } finally {
34 release();
35 }
36}
37
38// Debug race conditions:
39// 1. Add delays to expose timing issues
40// 2. Use conditional breakpoints based on state
41// 3. Log timestamps and call order
42// 4. Use thread/async debugging toolsSource Map Debugging:
1// Debugging minified/transpiled code
2// webpack.config.js
3module.exports = {
4 mode: 'development',
5 devtool: 'source-map', // Generate source maps
6 // ...
7};
8
9// TypeScript: tsconfig.json
10{
11 "compilerOptions": {
12 "sourceMap": true, // Generate .map files
13 "inlineSources": true
14 }
15}
16
17// VS Code automatically uses source maps
18// Browser DevTools → Settings → Enable JavaScript source mapsDebugging Tips and Tricks:
1// 1. Use debugger statement for quick breakpoints
2function quickDebug() {
3 debugger; // ← Pauses here when DevTools is open
4 // No need to find the line in DevTools
5}
6
7// 2. Conditional console logging
8const DEBUG = process.env.NODE_ENV === 'development';
9DEBUG && console.log('Debug info:', data);
10
11// 3. Object.freeze to catch mutations
12const config = Object.freeze({
13 apiUrl: 'https://api.example.com',
14 timeout: 5000
15});
16config.timeout = 10000; // TypeError in strict mode!
17
18// 4. console.table for arrays/objects
19const users = [
20 { id: 1, name: 'Alice', role: 'admin' },
21 { id: 2, name: 'Bob', role: 'user' }
22];
23console.table(users);
24// Displays as formatted table
25
26// 5. console.trace for call stack
27function deepFunction() {
28 console.trace('How did I get here?');
29}
30
31// 6. Performance markers
32performance.mark('start');
33expensiveOperation();
34performance.mark('end');
35performance.measure('operation', 'start', 'end');
36const measure = performance.getEntriesByName('operation')[0];
37console.log(`Operation took ${measure.duration}ms`);
38
39// 7. Network debugging
40// Chrome DevTools → Network tab
41// - Filter by type (XHR, JS, CSS, etc.)
42// - Inspect request/response headers
43// - View payload
44// - Check timing
45// - Throttle network speed
46
47// 8. Debugging compiled code
48// Use .map files to map back to source
49// Chrome can load source maps automatically
50// VS Code launch.json:
51{
52 "sourceMaps": true,
53 "outFiles": ["${workspaceFolder}/dist/**/*.js"]
54}Common Debugging Mistakes to Avoid:
1// ❌ Leaving breakpoints everywhere
2// ❌ Not checking the call stack
3// ❌ Debugging without understanding the code first
4// ❌ Making assumptions instead of verifying
5// ❌ Not reproducing the issue consistently
6// ❌ Changing multiple things at once
7// ❌ Not removing debug code before committing
8
9// ✅ Start with a hypothesis
10// ✅ Use minimal breakpoints
11// ✅ Verify your assumptions
12// ✅ Change one thing at a time
13// ✅ Document what you learn
14// ✅ Add tests to prevent regressionYour progress is saved in this browser, on this device only.