Labs ICT
Pro Login

Debugging

Debugging is an essential skill for JavaScript developers. Understanding debugging techniques, tools, and best practices can significantly improve your productivity and code quality.

In this comprehensive tutorial, we'll explore various debugging methods, from basic console logging to advanced debugging with browser developer tools, and learn how to debug both synchronous and asynchronous JavaScript code effectively.

Console Debugging

The console is your first and most accessible debugging tool. Mastering console methods is fundamental to effective JavaScript development.

Basic Console Methods

// console.log() - Basic logging
console.log('Hello, World!'); // "Hello, World!"
console.log(42); // 42
console.log({ name: 'John', age: 30 }); // { name: 'John', age: 30 }

// console.error() - Error logging
console.error('Something went wrong'); // Red error message
console.error(new Error('Detailed error')); // Error with stack trace

// console.warn() - Warning logging
console.warn('This feature is deprecated'); // Yellow warning message
console.warn('Performance warning: Large array detected'); // Warning with details

// console.info() - Information logging
console.info('Processing complete'); // Blue info message
console.info({ status: 'success', count: 100 }); // Info with object

// console.table() - Tabular data display
const users = [
  { name: 'John', age: 30 },
  { name: 'Jane', age: 25 },
  { name: 'Bob', age: 35 }
];
console.table(users);
// Displays formatted table with headers and rows

// console.time() and console.timeEnd() - Performance measurement
console.time('database-query');
// Database query code here
const result = performDatabaseQuery();
console.timeEnd('database-query');
console.log('Query took 150ms');

// console.group() and console.groupEnd() - Grouped logging
console.group('User processing');
console.log('Validating user data');
console.log('Processing user data');
console.groupEnd();

// console.count() - Count occurrences
console.count('button-click'); // button-click: 1
console.count('button-click'); // button-click: 2
console.count('button-click'); // button-click: 3

// console.clear() - Clear console
console.clear(); // Clears all console messages

// console.trace() - Stack trace
function traceFunction() {
  console.trace('Entering traceFunction');
  // Function implementation
  console.trace('Exiting traceFunction');
}

traceFunction();

// console.assert() - Conditional logging
function validateAge(age) {
  console.assert(age >= 18, 'User must be 18 or older');
  console.log('Age validation passed');
}

validateAge(25); // Passes assertion
validateAge(16); // Fails assertion with error message

// console.dir() - Object inspection
const user = { name: 'John', age: 30, address: { city: 'New York' } };
console.dir(user); // Displays object with expandable properties

// console.dirxml() - XML inspection
const xmlData = 'John';
console.dirxml(xmlData); // Displays XML structure

// Console formatting with CSS
console.log('%cCustom styled message', 'color: blue; font-size: 16px;', 'Regular message');
console.log('%cAnother styled message', 'color: red; font-weight: bold;', 'Error message');

Advanced Console Techniques

// Conditional logging
const DEBUG = process.env.NODE_ENV === 'development';

function debugLog(message, data) {
  if (DEBUG) {
    console.log(`DEBUG: ${message}`, data);
  }
}

debugLog('User data:', { id: 1, name: 'John' });

// Structured logging
const logger = {
  info: (message, data) => {
    console.log(`INFO: ${message}`, JSON.stringify(data, null, 2));
  },
  error: (message, error) => {
    console.error(`ERROR: ${message}`, error.message, error.stack);
  },
  warn: (message, data) => {
    console.warn(`WARN: ${message}`, JSON.stringify(data, null, 2));
  }
};

logger.info('User created', { id: 1, name: 'John' });
logger.error('Validation failed', new Error('Invalid input'));

// Performance logging
function measurePerformance(label, fn) {
  if (DEBUG) {
    console.time(label);
    const result = fn();
    console.timeEnd(label);
    console.log(`${label}:`, result);
  }
}

measurePerformance('array-sort', () => [1, 5, 3, 2].sort());

// Console filtering in browser
const originalLog = console.log;
const originalError = console.error;

// Custom logger for production
const productionLogger = {
  log: (message, data) => {
    // Send to logging service in production
    sendToLoggingService(message, data);
  },
  error: (message, error) => {
    // Send error to error tracking service
    sendToErrorService(message, error);
  }
};

// Conditional console usage
const logger = DEBUG ? console : productionLogger;

logger.log('Application started');
logger.error('Critical error', new Error('Database connection failed'));

// Console memory and performance
function analyzePerformance() {
  const memory = performance.memory;
  console.log('Memory usage:', memory);
  
  const entries = performance.getEntries();
  console.log('Performance entries:', entries);
}

// Console for different environments
const isDevelopment = process.env.NODE_ENV === 'development';
const isTesting = process.env.NODE_ENV === 'test';

if (isDevelopment) {
  // Development console with full logging
  console.log('Development mode - full logging enabled');
} else if (isTesting) {
  // Testing console with minimal logging
  console.warn('Testing mode - limited logging');
} else {
  // Production console with error-only logging
  console.error = (message) => {
    sendToErrorTracking(message);
  };
  console.log('Production mode - errors only');
}

// Console in Node.js
const fs = require('fs');

// Logging to file
function logToFile(message, data) {
  const timestamp = new Date().toISOString();
  const logEntry = `[${timestamp}] ${message} ${JSON.stringify(data)}\n`;
  
  fs.appendFileSync('app.log', logEntry);
}

// Console in browser with file logging
function setupFileLogging() {
  const originalLog = console.log;
  
  console.log = function(message, ...args) {
    originalLog(message, ...args);
    logToFile(message, args);
  };
}

// Console for mobile debugging
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

if (isMobile) {
  // Mobile-specific debugging
  console.log('Mobile debugging enabled');
  
  // Use remote debugging tools
  if (window.debugger) {
    window.debugger; // Breakpoint for mobile debugging
  }
}

// Console for Web Workers
// In main thread
console.log('Main thread: Processing data');

// In worker thread
if (typeof WorkerGlobalScope !== 'undefined') {
  WorkerGlobalScope.console.log('Worker thread: Processing data');
}

Browser Developer Tools

Modern browsers provide powerful developer tools for debugging JavaScript. Understanding these tools can dramatically improve your debugging efficiency.

Chrome DevTools

// Chrome DevTools shortcuts
// F12 - Toggle DevTools
// Ctrl+Shift+I - Toggle device toolbar
// Ctrl+Shift+C - Toggle console
// Ctrl+Shift+J - Toggle device toolbar
// Ctrl+Shift+D - Toggle dock side
// Ctrl+Shift+M - Toggle device mode
// Ctrl+Shift+E - Toggle element selection mode

// Elements panel debugging
const element = document.getElementById('my-element');
console.log(element); // Inspect element in Elements tab

// Console panel debugging
// Right-click on element -> "Inspect"
// Select element in Elements tab -> Right-click -> "Edit as HTML"

// Sources panel debugging
// Ctrl+P - Search files
// Ctrl+Shift+O - Go to file
// Ctrl+Shift+F - Search in file
// Ctrl+G - Go to line number
// Ctrl+Shift+G - Go to column

// Network panel debugging
const response = await fetch('/api/data');
console.log(response); // Inspect network request in Network tab

// Performance panel debugging
performance.mark('start-operation');
// Code to measure
performance.mark('end-operation');
performance.measure('operation-duration', 'start-operation', 'end-operation');

// Application panel debugging
// Local Storage inspection
console.log(localStorage); // Inspect in Application tab
console.log(sessionStorage); // Inspect in Application tab

// Breakpoints
function debugFunction() {
  debugger; // Sets breakpoint when DevTools is open
  const result = someCalculation();
  return result;
}

// Conditional breakpoints
function debugConditional(value) {
  if (value > 100) {
    debugger; // Only breaks when condition is met
  }
  return value * 2;
}

// Console in DevTools
// Use console.table() for data inspection
const data = [
  { id: 1, name: 'John', status: 'active' },
  { id: 2, name: 'Jane', status: 'inactive' }
];
console.table(data);

// Command Line API
// Execute commands in Console panel
console.log('document.title'); // Get page title
console.log('window.location'); // Get current URL
console.dir(document.body); // Inspect body element

// Live expressions
// Use $0, $1, etc. to reference elements
// $0 refers to last selected element
// $1 refers to second last selected element
document.addEventListener('click', (e) => {
  console.log('Clicked element:', e.target);
  $0 = e.target; // Reference clicked element
});

Firefox Developer Tools

// Firefox Developer Tools features
// F12 - Toggle Developer Tools
// Ctrl+Shift+K - Toggle browser console
// Ctrl+Shift+L - Toggle web console

// Firefox-specific features
// Memory tool
// Performance tool
// Network monitor
// Storage inspector

// Console commands
// :help - List available commands
// :clear - Clear console
// :cd directory - Change working directory

// Responsive Design Mode
// Toggle with device icon in DevTools
// Test mobile layouts

// Web Console
// Browser console accessible via Web Console
// Multi-tab console management

Safari Web Inspector

// Safari Web Inspector features
// Develop menu -> Show Web Inspector
// Option+Cmd+C - Toggle Web Inspector

// Safari-specific features
// Timeline panel for performance
// Network panel for debugging requests
// Storage inspector for localStorage/sessionStorage
// Console with auto-complete

// Console commands
// $() - Access last result
// $_ - Last result
// $0, $1, etc. - Reference elements

// Safari Responsive Design Mode
// Test different device sizes
// Debug mobile layouts

// Remote debugging
// Safari can connect to iOS devices for debugging
// Web Inspector available on iOS Safari

Edge Developer Tools

// Edge Developer Tools features
// F12 - Toggle Developer Tools
// Ctrl+Shift+I - Toggle device toolbar
// Ctrl+Shift+2 - Toggle emulation

// Edge-specific features
// Network condition throttling
// Service Worker debugging
// Memory analysis tools

// Console integration
// Edge console supports modern JavaScript features
// Node.js compatibility in Edge console

// Performance analysis
// Performance profiler
// CPU profiling
// Memory profiling

// Remote debugging
// Edge supports remote debugging of Windows applications

Source Maps

Source maps are files that map your compiled, minified, or transpiled code back to your original source code, making debugging much easier in production.

Understanding Source Maps

// What is a source map?
{
  "version": 3,
  "file": "app.min.js.map",
  "sourceRoot": "/src/",
  "sources": ["app.js", "utils.js"],
  "sourcesContent": ["// Original source code", "// Utility functions"]
}

// Source map benefits
// 1. Debug production code as if it were development code
// 2. Better error stack traces with original filenames and line numbers
// 3. Improved debugging experience in browser DevTools
// 4. Code obfuscation doesn't hide logic errors

// Source map generation
// Generated by bundlers like Webpack, Rollup, or Vite
// Can be generated manually for custom build processes

// Browser support for source maps
// Chrome: Full support
// Firefox: Full support
// Safari: Full support
// Edge: Full support

// Using source maps in browser
// Enable source maps in browser settings
// DevTools will automatically load source maps when available
// Error stack traces show original source locations

Source Map Configuration

// Webpack configuration for source maps
module.exports = {
  devtool: 'source-map',
  devtoolModule: '@webpack/webpack-sources',
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: 'bundle.min.js',
    path: './dist/',
    sourceMapFilename: 'bundle.min.js.map'
  }
};

// Vite configuration for source maps
export default {
  build: {
    sourcemap: true,
    minify: 'terser',
    rollupOptions: {
      output: {
        sourcemap: true,
        sourcemapFile: 'bundle.min.js.map'
      }
    }
  }
};

// Custom source map generation
function generateSourceMap(sources, file) {
  const generator = new SourceMapGenerator({
    file: file
    sourceRoot: '/src'
  });
  
  sources.forEach(source => {
    generator.addSource(source, source.length);
  });
  
  return generator.toJSON();
}

// Source map validation
function validateSourceMap(sourceMap) {
  return sourceMap.version === 3 && 
         sourceMap.sources && 
         sourceMap.sources.length > 0;
}

Breakpoints

Breakpoints allow you to pause code execution at specific lines and inspect the program state, making them invaluable for debugging complex issues.

Types of Breakpoints

// Line breakpoints
function calculateTotal(numbers) {
  debugger; // Execution pauses here
  let total = 0;
  
  for (let i = 0; i < numbers.length; i++) {
    total += numbers[i];
  }
  
  return total;
}

// Conditional breakpoints
function processData(data) {
  if (data.type === 'error') {
    debugger; // Only break for error conditions
  }
  
  return data.value * 2;
}

// Function breakpoints
function complexCalculation(x, y) {
  debugger; // Break at start of function
  const result = x * y + Math.sqrt(x);
  debugger; // Break after first calculation
  const finalResult = result + y;
  debugger; // Break after second calculation
  return finalResult;
}

// DOM breakpoints
const button = document.getElementById('debug-button');
button.addEventListener('click', (event) => {
  debugger; // Break when button is clicked
  console.log('Button clicked:', event.target);
});

// Event listener breakpoints
document.addEventListener('click', (event) => {
  if (event.target.classList.contains('debug-target')) {
    debugger; // Break when specific element is clicked
  }
});

// Exception breakpoints
try {
  riskyOperation();
} catch (error) {
  debugger; // Break when exception is caught
  console.error('Caught error:', error);
}

// Logpoint breakpoints
console.log('Starting operation...');
debugger; // Breakpoint with context
console.log('Operation completed');

// DOM mutation breakpoints
const observer = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    if (mutation.target.id === 'debug-target') {
      debugger; // Break when target element changes
    }
  });
});

observer.observe(document.getElementById('debug-target'), {
  childList: true,
  attributes: true
});

Breakpoint Management

// Browser DevTools breakpoints
// Chrome: Click line number in Sources panel
// Chrome: Right-click line number -> "Add breakpoint"
// Chrome: Breakpoints panel shows all active breakpoints

// Firefox: Debugger panel
// Firefox: Click line number in Debugger panel
// Firefox: Breakpoints panel shows all active breakpoints

// Conditional breakpoints
// Chrome: Right-click line number -> "Add conditional breakpoint"
// Set condition: x > 10
// Code only breaks when x is greater than 10

// Breakpoint conditions
function debugWithCondition(value) {
  debugger; // Conditional breakpoint
  return value * 2;
}

// Expression breakpoints
// Evaluate expressions that must be true for breakpoint to trigger
// Chrome: Right-click -> "Add conditional breakpoint"
// Expression: users.length > 0

// Logpoint breakpoints
// Break when console.log is called with specific message
// Chrome: Add logpoint with condition: console.log('Debug point reached')
// Only breaks when message matches exactly

// Event listener breakpoints
// Break when specific event is fired
// Chrome: Event Listener Breakpoints panel
// Break on click, keydown, or any DOM event

// DOM mutation breakpoints
// Break when DOM tree changes
// Chrome: Dom Breakpoints panel
// Monitor specific elements or subtree changes

// XHR/Fetch breakpoints
// Break when network requests are made
// Chrome: Network panel -> Right-click request -> "Break on request"
// Can set breakpoints on response headers or body

// Async function breakpoints
// Break on async function entry points
// Chrome: Async stack trace shows async function calls
// Break at function start or await expressions

Advanced Breakpoint Techniques

// Debugging with watch expressions
// Chrome: Watch panel
// Add variable to watch: user
// Expression: user.name === 'admin'
// Breaks when user.name changes to 'admin'

// Remote debugging
// VS Code remote debugging
// Chrome: chrome://inspect/[target-id]
// VS Code: Attach to process with --inspect flag

// Node.js debugging
// Node.js inspector
// node inspect script.js --inspect-brk [script.js]

// Node.js debugging with Chrome
// Open chrome://inspect and connect to Node.js process

// Mobile debugging
// Chrome DevTools for mobile
// Safari Web Inspector for iOS
// Android Studio for Android development

// Production debugging
// Source maps with original source
// Error reporting services
// Conditional debug logging
// Performance monitoring

Asynchronous Debugging

Debugging asynchronous JavaScript code requires special techniques to handle callbacks, promises, and async/await patterns effectively.

Debugging Callbacks

// Callback hell debugging
function step1(callback) {
  console.log('Step 1');
  setTimeout(() => {
    console.log('Step 2');
    callback();
  }, 100);
}

function step2(callback) {
  console.log('Step 3');
  setTimeout(() => {
    console.log('Step 4');
    callback();
  }, 100);
}

step1(() => {
  step2(() => {
    step3(() => {
      console.log('All steps completed');
    });
  });
});

// Named callbacks for better stack traces
function fetchData(callback) {
  console.log('Starting fetch');
  setTimeout(() => {
    console.log('Fetch completed');
    callback({ data: 'response data' });
  }, 1000);
}

// Error handling in callbacks
function processData(data, callback) {
  try {
    const result = JSON.parse(data);
    callback(null, result);
  } catch (error) {
    console.error('Parse error:', error);
    callback(error);
  }
}

// Promise debugging
function asyncOperation() {
  return new Promise((resolve, reject) => {
    try {
      const result = riskyOperation();
      resolve(result);
    } catch (error) {
      reject(error);
    }
  });
}

asyncOperation()
  .then(result => {
    console.log('Success:', result);
  })
  .catch(error => {
    console.error('Promise rejected:', error);
  });

// Async/await debugging
async function sequentialOperations() {
  try {
    console.log('Starting step 1');
    const result1 = await step1();
    console.log('Step 1 completed:', result1);
    
    console.log('Starting step 2');
    const result2 = await step2();
    console.log('Step 2 completed:', result2);
    
    console.log('Starting step 3');
    const result3 = await step3();
    console.log('Step 3 completed:', result3);
  } catch (error) {
    console.error('Sequential operation failed:', error);
  }
}

// Promise.all debugging
async function parallelOperations() {
  try {
    const results = await Promise.all([
      operation1(),
      operation2(),
      operation3()
    ]);
    console.log('All operations completed:', results);
  } catch (error) {
    console.error('Parallel operations failed:', error);
  }
}

// Promise.race debugging
async function raceOperations() {
  try {
    const result = await Promise.race([
      fastOperation(),
      slowOperation()
    ]);
    console.log('Race completed:', result);
  } catch (error) {
    console.error('Race operation failed:', error);
  }
}

// Generator debugging
function* dataGenerator() {
  console.log('Generator started');
  
  for (let i = 0; i < 5; i++) {
    console.log(`Yielding ${i}`);
    yield i;
    debugger; // Breakpoint in generator
  }
  
  console.log('Generator completed');
}

const generator = dataGenerator();
console.log('Generated values:');
for (const value of generator) {
  console.log(value);
}

Event Loop Debugging

// Event loop debugging
let isProcessing = false;

function startProcessing() {
  if (isProcessing) {
    console.warn('Processing already in progress');
    return;
  }
  
  isProcessing = true;
  console.log('Processing started');
  
  // Simulate async work
  setTimeout(() => {
    isProcessing = false;
    console.log('Processing completed');
  }, 2000);
}

// Event loop debugging with queue
const taskQueue = [];
let isProcessing = false;

function processQueue() {
  if (isProcessing || taskQueue.length === 0) {
    return;
  }
  
  isProcessing = true;
  console.log('Processing queue, size:', taskQueue.length);
  
  while (taskQueue.length > 0) {
    const task = taskQueue.shift();
    console.log('Processing task:', task);
    // Simulate async task
    setTimeout(() => {
      console.log('Task completed:', task);
      isProcessing = false;
    }, 100);
  }
}

// Event loop debugging with microtasks
function processWithMicrotasks() {
  console.log('Processing with microtasks');
  
  Promise.resolve().then(() => {
    console.log('Microtask 1 completed');
  }).then(() => {
    console.log('Microtask 2 completed');
  });
  
  console.log('All microtasks completed');
}

// Async/await in event loops
async function eventLoopExample() {
  while (true) {
    console.log('Event loop iteration');
    
    await new Promise(resolve => setTimeout(resolve, 100));
    
    if (shouldStop()) {
      break;
    }
  }
}

function shouldStop() {
  return Math.random() < 0.1; // Randomly stop 10% of the time
}

// Event loop debugging with requestAnimationFrame
function animationLoop() {
  let frame = 0;
  
  function animate() {
    console.log(`Animation frame: ${frame}`);
    frame++;
    
    if (frame < 60) {
      requestAnimationFrame(animate);
    } else {
      console.log('Animation completed');
    }
  }
  
  animate();
}

Performance Debugging

Performance debugging helps identify bottlenecks, memory leaks, and optimization opportunities in your JavaScript applications.

Performance Profiling

// Performance profiling
function profileFunction() {
  console.profile('myFunction');
  
  // Function to profile
  const result = [];
  for (let i = 0; i < 1000; i++) {
    result.push(i * i);
  }
  
  console.profileEnd('myFunction');
  return result;
}

// Memory profiling
function profileMemoryUsage() {
  console.profile('memory-usage');
  
  const data = [];
  for (let i = 0; i < 1000; i++) {
    data.push(new Array(1000).fill(0));
  }
  
  console.profileEnd('memory-usage');
  console.log('Memory usage:', data);
  
  // Force garbage collection if available
  if (global.gc) {
    global.gc();
  }
}

// CPU profiling
function profileCPUUsage() {
  console.profile('cpu-usage');
  
  // CPU-intensive task
  let result = 0;
  for (let i = 0; i < 1000000; i++) {
    result += Math.sqrt(i);
  }
  
  console.profileEnd('cpu-usage');
  console.log('CPU usage result:', result);
}

// Network performance profiling
function profileNetworkRequests() {
  console.profile('network-requests');
  
  const requests = [];
  for (let i = 0; i < 100; i++) {
    const start = performance.now();
    fetch('/api/data').then(() => {
      const end = performance.now();
      requests.push(end - start);
    });
  }
  
  Promise.all(requests).then(() => {
    console.profileEnd('network-requests');
    console.log('Network requests completed:', requests);
  });
}

// Using performance profiling
profileFunction();
profileMemoryUsage();
profileCPUUsage();
profileNetworkRequests();

// Performance timing
function measurePerformance() {
  const start = performance.now();
  
  // Code to measure
  const result = expensiveOperation();
  
  const end = performance.now();
  console.log(`Operation took ${end - start}ms`);
  
  return result;
}

// Performance marks
performance.mark('start-operation');
// Code to measure
const result = processData(data);
performance.mark('end-operation');

performance.measure('operation-duration', 'start-operation', 'end-operation');
console.log('Operation duration measured');

// Performance observer
const observer = new PerformanceObserver((list) => {
  list.forEach(entry => {
    if (entry.entryType === 'measure') {
      console.log('Performance entry:', entry);
    }
  });
});

observer.observe({ entryTypes: ['measure', 'navigation'] });

// Memory debugging
function findMemoryLeaks() {
  const leaks = [];
  
  function createLeak() {
    const data = new Array(1000).fill(0);
    return data; // Intentionally create a leak
  }
  
  for (let i = 0; i < 10; i++) {
    leaks.push(createLeak());
  }
  
  // Check memory usage
  if (performance.memory) {
    console.log('Memory before:', performance.memory.usedJSHeapSize);
  }
  
  // Force garbage collection
  if (global.gc) {
    global.gc();
  }
  
  // Check memory after cleanup
  if (performance.memory) {
    console.log('Memory after:', performance.memory.usedJSHeapSize);
    console.log('Potential leaks:', leaks.length);
  }
}

// Performance budgeting
class PerformanceBudget {
  constructor(budget = 50) {
    this.budget = budget;
    this.used = 0;
  }
  
  measure(operation) {
    const start = performance.now();
    const result = operation();
    const end = performance.now();
    
    this.used += (end - start);
    
    if (this.used > this.budget) {
      console.warn(`Performance budget exceeded: ${this.used}ms`);
    }
    
    return result;
  }
}

const budget = new PerformanceBudget(100);
budget.measure(() => expensiveOperation());
budget.measure(() => anotherExpensiveOperation());

Debugging Best Practices

Best practices for effective and maintainable debugging in JavaScript development.

Debugging Workflow

// 1. Reproduce the issue consistently
function reproduceBug() {
  // Steps to reproduce the bug
  console.log('Step 1: Navigate to page');
  console.log('Step 2: Click the button');
  console.log('Step 3: Check the result');
}

// 2. Isolate the problem
function isolateProblem() {
  // Test with minimal code
  const minimalResult = minimalReproduction();
  console.log('Minimal reproduction:', minimalResult);
  
  // Test with different inputs
  const edgeCaseResult = edgeCaseReproduction();
  console.log('Edge case reproduction:', edgeCaseResult);
}

// 3. Fix and verify
function fixAndVerify() {
  // Apply fix
  const fixResult = applyFix();
  console.log('Fix result:', fixResult);
  
  // Verify fix works
  const verification = verifyFix();
  console.log('Fix verification:', verification);
}

// 4. Document the solution
function documentSolution() {
  // Create documentation for the fix
  console.log('Bug documented in README');
  console.log('Solution steps:');
  console.log('1. Reproduce: ...');
  console.log('2. Isolate: ...');
  console.log('3. Fix: ...');
  console.log('4. Verify: ...');
}

// 5. Add regression tests
function testFix() {
  // Test that the fix works
  if (testFixWorks()) {
    console.log('Fix verified: Bug is resolved');
  } else {
    console.error('Fix verification failed: Bug still exists');
  }
}

// Debugging checklist
function debugChecklist(issue) {
  const checklist = [
    'Can I reproduce the issue?',
    'Have I isolated the root cause?',
    'Have I implemented a fix?',
    'Does the fix resolve the issue?',
    'Have I added regression tests?',
    'Is the fix documented?'
  ];
  
  checklist.forEach((item, index) => {
    console.log(`${index + 1}. ${item}: ${issue[item] ? 'YES' : 'NO'}`);
  });
}

// 6. Use version control
function debugWithVersionControl() {
  const currentVersion = getCurrentVersion();
  const gitHash = getCurrentGitHash();
  
  console.log('Current version:', currentVersion);
  console.log('Git hash:', gitHash);
  console.log('Environment:', process.env.NODE_ENV);
  
  // Check if issue exists in current version
  if (bugExistsInVersion(currentVersion)) {
    console.log('Bug exists in current version');
  } else {
    console.log('Bug not found in current version');
  }
}

Production Debugging

// Production debugging considerations
// 1. Remove debug code from production
const DEBUG = process.env.NODE_ENV !== 'production';

function log(message) {
  if (DEBUG) {
    console.log('DEBUG:', message);
  }
}

// 2. Use appropriate log levels
function logInfo(message) {
  console.log('INFO:', message);
}

function logError(message) {
  console.error('ERROR:', message);
}

function logWarning(message) {
  console.warn('WARNING:', message);
}

// 3. Implement error boundaries
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  componentDidCatch(error) {
    this.setState({ hasError: true, error });
    
    // Send error to logging service
    logError('Error boundary caught error:', error);
  }
  
  render() {
    if (this.state.hasError) {
      return 
Error: {this.state.error.message}
; } return this.props.children; } } // 4. Use source maps // Configure webpack for production builds const config = { mode: 'production', devtool: 'source-map' }; // 5. Implement error reporting function reportError(error) { // Send to error tracking service sendToErrorService({ message: error.message, stack: error.stack, timestamp: new Date().toISOString(), userAgent: navigator.userAgent, url: window.location.href }); } // 6. Monitor performance class PerformanceMonitor { constructor() { this.metrics = { errors: 0, warnings: 0, operations: 0 }; } recordError(error) { this.metrics.errors++; this.sendToMonitoringService(error); } getMetrics() { return this.metrics; } } const monitor = new PerformanceMonitor(); try { riskyOperation(); } catch (error) { monitor.recordError(error); } // 7. Use feature flags const DEBUG = process.env.DEBUG || false; const VERBOSE_LOGGING = process.env.VERBOSE_LOGGING || false; function debugLog(message) { if (DEBUG) { console.log('DEBUG:', message); } else if (VERBOSE_LOGGING) { console.log('VERBOSE:', message); } }

Mobile Debugging

Mobile debugging presents unique challenges and requires specialized tools and techniques for effective debugging on mobile devices.

Mobile-Specific Debugging

// iOS Safari debugging
// Connect iOS device to Mac Safari
// 1. Enable Web Inspector on iOS device
// 2. Open Safari on Mac
// 3. Use Develop menu -> Web Inspector -> [device name]
// 4. Check Console in Safari

// Android debugging
// Chrome DevTools for Android
// 1. Enable USB debugging
// 2. Connect Android device via USB
// 3. Use Chrome DevTools for Android

// React Native debugging
// React Native debugging
// 1. Use React DevTools
// 2. Use Flipper for React Native
// 3. Use Expo debugging tools

// Chrome DevTools mobile emulation
// Device toolbar -> Toggle device mode
// Network throttling
// CPU throttling

// Mobile-specific debugging techniques
// Touch event debugging
// Orientation change debugging
// Battery and performance debugging

// Mobile console debugging
// vConsole for iOS
// Chrome remote debugging for Android
// LogCat for Android

// Performance monitoring
// Mobile performance profiling
// Memory usage tracking
// Network request monitoring

// Mobile-specific breakpoints
// Touch event breakpoints
// Orientation change breakpoints
// Lifecycle event debugging

Debugging Tools and Libraries

Exploring popular debugging tools and libraries that can enhance your debugging workflow and productivity.

Browser Extensions

// React Developer Tools
// React Developer Tools browser extension
// Redux DevTools extension
// Apollo Client Developer Tools extension

// Chrome DevTools extensions
// React Developer Tools
// Redux DevTools
// Vue.js devtools
// Angular DevTools

// Debugging extensions
// Debugger for Chrome
// React Perf
// Lighthouse
// Web Vitals

// Node.js debugging
// Node.js Inspector
// nodemon for auto-restart
// Chrome DevTools for Node.js

// VS Code extensions
// JavaScript Debugger
// TypeScript Hero

// Standalone debugging tools
// Charles Proxy
// Fiddler
// Wireshark

Logging Libraries

// Winston logging
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'app.log' })
  ]
});

logger.info('Application started');
logger.error('Error occurred', new Error('Something went wrong'));

// Pino logging
const pino = require('pino');

const logger = pino({
  level: 'info',
  browser: { asObject: true },
  prettyPrint: true
});

logger.info('Application started');
logger.error('Error occurred', new Error('Something went wrong'));

// Debug module
const debug = require('debug')('my-app');

debug('Debug message');
debug('Error message', new Error('Something went wrong'));

// Loglevel
const loglevel = require('loglevel');

const logger = loglevel.getLogger('my-app');

logger.info('Application started');
logger.error('Error occurred', new Error('Something went wrong'));

// Morgan HTTP request logging
const morgan = require('morgan');

const app = express();
app.use(morgan('combined'));
app.use((req, res, next) => {
  next();
});

// Error tracking services
// Sentry
const Sentry = require('@sentry/browser');

Sentry.init({
  dsn: 'your-dsn',
  environment: process.env.NODE_ENV
});

Sentry.captureException(new Error('Something went wrong'));

// Bugsnag
const Bugsnag = require('@bugsnag/js');

Bugsnag.start({
  apiKey: 'your-api-key',
  appVersion: '1.0.0'
});

Bugsnag.notify(new Error('Something went wrong'));

// LogRocket
const LogRocket = require('logrocket');

LogRocket.init('your-api-key');

LogRocket.track('Something went wrong', {
  level: 'error',
  tags: ['debug', 'authentication']
});

// Raygun
const Raygun = require('raygun');

Raygun.init('your-api-key');

Raygun.trackError(new Error('Something went wrong'), {
  tags: ['debug', 'authentication']
});

Summary

Key Takeaways

  • Console debugging is fundamental and accessible to all developers
  • Browser DevTools provide powerful debugging capabilities
  • Source maps enable debugging production code
  • Breakpoints allow pausing execution and inspecting state
  • Async debugging requires understanding of promises and async/await
  • Performance profiling helps identify bottlenecks and optimization opportunities
  • Mobile debugging requires specialized tools and techniques
  • Debugging tools and libraries enhance productivity
  • Source maps connect minified code back to original source

Best Practices

  • Use appropriate log levels for different environments
  • Implement proper error boundaries in frontend applications
  • Use source maps for production debugging
  • Master breakpoints for efficient debugging
  • Use performance profiling to identify bottlenecks
  • Follow systematic debugging workflow
  • Remove debug code from production builds
  • Use error reporting services for production monitoring
  • Document bugs and fixes for future reference
  • Use appropriate debugging tools for your development environment

Common Pitfalls

  • Leaving console.log statements in production code
  • Not removing breakpoints after debugging is complete
  • Not using appropriate error handling for async operations
  • Not documenting bugs and fixes properly
  • Not using source maps in production builds
  • Not following systematic debugging workflow
  • Over-relying on alert() for debugging
  • Not considering performance implications of debugging code
  • Not removing debug code before deployment