Debugging and Best Practices in JavaScript

Learn JavaScript debugging and best practices with clear explanations and beginner-friendly examples. This guide covers using browser DevTools, understanding and fixing common JavaScript errors, organizing code effectively, writing meaningful comments, and following clean coding principles to build readable, maintainable, and bug-free applications.

What is Debugging in JavasScript ?

Debugging is the process of observing how JavaScript executes code and correcting incorrect assumptions. JavaScript runs top to bottom, executing each statement in order. When the output is wrong, debugging helps you locate where the logic deviates from expectation.

Using Browser DevTools

Browser Developer Tools are essential instruments for web developers, providing powerful capabilities for debugging, testing, and optimizing web applications. Every modern browser includes these built-in tools that help developers inspect code, monitor performance, and troubleshoot issues efficiently.

Opening DevTools

Accessing developer tools is straightforward across all major browsers. You can open them by pressing F12 on Windows or Linux, or Command + Option + I on Mac. Alternatively, right-click anywhere on a webpage and select "Inspect" or "Inspect Element" from the context menu.

The Console Tab

The Console serves as your primary communication channel with the browser's JavaScript engine. It displays error messages, warnings, and logs from your code execution.

Example - Basic Console Usage:
javascript
let name = "John";
let age = 20;

console.log(name);
console.log(age);

What You See in the Console

John
20

Simple Error Example

javascript
console.log(total);

Console Output

This error appears when JavaScript cannot find a variable that has not been declared or initialized.

ReferenceError: total is not defined
  

The Elements Panel

The Elements or Inspector panel reveals the complete HTML structure of your webpage and allows real-time manipulation of the DOM. You can click any element to view its HTML markup, CSS styles, computed properties, and box model dimensions.

Example - Inspecting and Modifying Elements:

javascript
// In the Console, you can manipulate selected elements
// After selecting an element in the Elements panel, $0 refers to it
$0.style.backgroundColor = 'red';
$0.classList.add('highlight');

// Query and modify elements directly
document.querySelector('.header').style.display = 'none';

Practical Use Case

When your button isn't centered properly, use the Elements panel to:

  • Inspect the button element
  • Check applied CSS styles
  • Identify margin, padding, or flexbox issues
  • Test CSS changes in real time

The Network Tab

The Network panel monitors all HTTP requests made by your webpage. Each request displays timing information, status codes, headers, response bodies, and size metrics.

javascript
// Make an API request
fetch('https://api.example.com/users/1')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('API Error:', error));
<section> <h4>How to Debug in the Network Tab</h4> <ol> <li>Open the <strong>Network</strong> tab in Developer Tools</li> <li>Refresh the page to load network requests</li> <li>Find your API request in the list</li> <li>Click on the request to view details:</li> </ol> <ul> <li><strong>Headers:</strong> View request and response information</li> <li><strong>Response:</strong> Check returned data from the server</li> <li><strong>Timing:</strong> Analyze how long the request took</li> <li> <strong>Status Code:</strong> <ul> <li><code>404</code> – Resource not found</li> <li><code>500</code> – Server error</li> </ul> </li> </ul> </section>

The Sources/Debugger Panel

The Sources panel provides comprehensive debugging capabilities for JavaScript code. You can set breakpoints on specific lines, which pause code execution.

Example - Using Breakpoints:

javascript
function calculateTotal(items) {
  let total = 0;
  
  for (let item of items) {
    // Set a breakpoint on the next line to inspect each item
    total += item.price * item.quantity;
  }
  
  return total;
}

const cart = [
  { name: 'Apple', price: 2, quantity: 3 },
  { name: 'Banana', price: 1, quantity: 5 }
];

console.log(calculateTotal(cart));

Debugging Steps

  1. Open the Sources panel in Developer Tools
  2. Find your JavaScript file
  3. Click the line number to set a breakpoint
  4. Refresh the page
  5. When the code pauses:
    • Hover over variables to see their values
    • Use Step Over (F10) to move to the next line
    • Use Step Into (F11) to enter function calls

The Application/Storage Tab

javascript
// Save data to localStorage
localStorage.setItem("username", "John");

// Read data from localStorage
console.log(localStorage.getItem("username"));

Console Output

John
  1. Open Developer Tools
  2. Go to the Application tab
  3. Click Local Storage
  4. Select your website
  5. You will see:
  • Key: username
  • Value: John

Common Errors and How to Fix Them

Syntax Errors

Syntax errors occur when your code violates JavaScript's grammatical rules, preventing the script from executing at all.

Example 1 - Missing Closing Bracket:

javascript
// WRONG - Missing closing brace
function greet(name) {
  console.log('Hello, ' + name);
// Missing }

// CORRECT
function greet(name) {
  console.log('Hello, ' + name);
}

Example 2 - Missing Quotes:

javascript
// WRONG - Unclosed string
const message = 'Hello World;

// CORRECT
const message = 'Hello World';

Example 3 - Mismatched Brackets:

javascript
// WRONG - Using wrong bracket type
const numbers = [1, 2, 3};

// CORRECT
const numbers = [1, 2, 3];

How to Fix Syntax Errors in JavaScript

Fixing syntax errors in JavaScript is the first step in debugging. These errors occur when the code structure is incorrect and prevent the program from running. Follow these best practices to quickly identify and resolve syntax issues.

  • Use a code editor with syntax highlighting to spot mistakes easily
  • Check the line number mentioned in the error message
  • Ensure all brackets, parentheses, and quotes are properly closed
  • Use automatic code formatting tools to maintain clean structure

Reference Errors

Reference errors happen when you try to access a variable or function that doesn't exist in the current scope.

Example 1 - Typo in Variable Name:

javascript
// WRONG
const userName = 'Alice';
console.log(username); // ReferenceError: username is not defined

// CORRECT
const userName = 'Alice';
console.log(userName); // Notice the capital N

Example 2 - Using Variable Before Declaration:

javascript
// WRONG
console.log(age); // ReferenceError: Cannot access 'age' before initialization
const age = 25;

// CORRECT
const age = 25;
console.log(age);

Type Errors

Type errors occur when you perform operations on values of incompatible types.

Example 1 - Calling Method on Null/Undefined:

javascript
// WRONG
const user = null;
console.log(user.name); // TypeError: Cannot read property 'name' of null

// CORRECT - Check before accessing
const user = null;
if (user) {
  console.log(user.name);
} else {
  console.log('User not found');
}

// CORRECT - Using optional chaining
const user = null;
console.log(user?.name); // Returns undefined instead of error

Example 2 - Treating Non-Function as Function:

javascript
// WRONG
const greeting = 'Hello';
greeting(); // TypeError: greeting is not a function

// CORRECT - Check type before calling
const greeting = 'Hello';
if (typeof greeting === 'function') {
  greeting();
} else {
  console.log(greeting);
}

Example 3 - Array Method on Non-Array:

javascript
// WRONG
const notAnArray = 'hello';
notAnArray.push('world'); // TypeError: notAnArray.push is not a function

// CORRECT - Ensure it's an array
const myArray = [];
myArray.push('hello');
myArray.push('world');

Logic Errors

Logic errors are bugs where code runs without errors but produces wrong results.

Example 1 - Assignment Instead of Comparison:

javascript
// WRONG - Using = instead of ===
let score = 50;
if (score = 100) { // This assigns 100 to score!
  console.log('Perfect score!');
}

// CORRECT
let score = 50;
if (score === 100) {
  console.log('Perfect score!');
}

Example 2- Incorrect Order of Operations:

javascript
// WRONG - Missing parentheses
const total = 10 + 5 * 2; // Result: 20 (multiplication first)

// CORRECT - When you want addition first
const total = (10 + 5) * 2; // Result: 30

Asynchronous Errors

Asynchronous code introduces unique challenges because operations don't execute in the order they appear.

javascript
// WRONG - No error handling
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data));
// If the request fails, error goes unhandled

// CORRECT - With error handling
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Failed to fetch:', error));

Null and Undefined Errors

Example - Safe Property Access:

javascript
// WRONG - Direct access
const data = null;
console.log(data.name); // TypeError

// CORRECT - Option 1: Check first
const data = null;
if (data) {
  console.log(data.name);
}

// CORRECT - Option 2: Optional chaining
const data = null;
console.log(data?.name); // undefined (no error)

// CORRECT - Option 3: Default values
const data = null;
const name = data?.name ?? 'Unknown'; // 'Unknown'

Code Organization and Comments

Well-organized code with appropriate comments enhances maintainability and collaboration.

File and Folder Structure

Example - Project Structure:
Screenshot 2025-12-19 163138.png

Module Organization

Example - Proper Module Structure:

javascript
// userService.js - Focused on user-related operations
export const getUser = async (userId) => {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
};

export const updateUser = async (userId, userData) => {
  const response = await fetch(`/api/users/${userId}`, {
    method: 'PUT',
    body: JSON.stringify(userData)
  });
  return response.json();
};

export const deleteUser = async (userId) => {
  await fetch(`/api/users/${userId}`, { method: 'DELETE' });
};

// main.js - Using the module
import { getUser, updateUser } from './services/userService.js';

const user = await getUser(123);
console.log(user);

Function Organization

Example - Single Responsibility Functions:

javascript
// WRONG - Function doing too much
function processUserAndSendEmail(userData) {
  // Validate
  if (!userData.email) return false;
  
  // Save to database
  database.save(userData);
  
  // Send email
  sendEmail(userData.email, 'Welcome!');
  
  // Log activity
  console.log('User processed');
}

// CORRECT - Separate concerns
function validateUser(userData) {
  return userData.email && userData.name;
}

function saveUser(userData) {
  return database.save(userData);
}

function sendWelcomeEmail(email) {
  return sendEmail(email, 'Welcome!');
}

function logUserActivity(action, userId) {
  console.log(`${action}: User ${userId}`);
}

// Main function orchestrates
async function registerUser(userData) {
  if (!validateUser(userData)) {
    throw new Error('Invalid user data');
  }
  
  const user = await saveUser(userData);
  await sendWelcomeEmail(user.email);
  logUserActivity('registered', user.id);
  
  return user;
}

Comment Guidelines

Example - Good vs Bad Comments:

javascript
// BAD - Obvious comment
// Increment counter by 1
counter++;

// BAD - Restating code
// Loop through users array
for (let user of users) {
  // Print user name
  console.log(user.name);
}

// GOOD - Explaining WHY
// Using binary search because the array is sorted and contains 10,000+ items
// Linear search would be too slow for this use case
const index = binarySearch(sortedArray, targetValue);

// GOOD - Explaining business logic
// Users under 18 cannot make purchases per legal requirements
if (user.age < 18) {
  throw new Error('User must be 18 or older');
}

// GOOD - Documenting workaround
// Safari doesn't support this API yet, using polyfill
// TODO: Remove this when Safari adds support (check caniuse.com)
if (!window.someAPI) {
  loadPolyfill();
}

// GOOD - Warning about gotchas
// Note: This function modifies the original array
// If you need the original, pass a copy instead
function sortItems(items) {
  return items.sort((a, b) => a.price - b.price);
}

Documentation Comments (JSDoc)

Example - Documenting Functions:

javascript
/**
 * Calculates the total price including tax
 * @param {number} price - The base price
 * @param {number} taxRate - Tax rate as decimal (e.g., 0.08 for 8%)
 * @returns {number} The total price with tax included
 * @example
 * calculateTotal(100, 0.08) // Returns 108
 */
function calculateTotal(price, taxRate) {
  return price * (1 + taxRate);
}

/**
 * Fetches user data from the API
 * @param {string} userId - The unique user identifier
 * @returns {Promise<Object>} User object containing name, email, etc.
 * @throws {Error} If user is not found or network fails
 */
async function fetchUser(userId) {
  const response = await fetch(`/api/users/${userId}`);
  if (!response.ok) {
    throw new Error('User not found');
  }
  return response.json();
}

Writing Clean, Readable Code

Clean code minimizes cognitive load, reduces bugs, and accelerates development.

Meaningful Naming Conventions

Example - Descriptive Names:

javascript
// BAD - Unclear names
const d = new Date();
const x = u.filter(i => i.a > 18);

function calc(a, b) {
  return a * b * 0.08;
}

// GOOD - Clear, descriptive names
const currentDate = new Date();
const adultUsers = users.filter(user => user.age > 18);

function calculateSalesTax(price, quantity) {
  const TAX_RATE = 0.08;
  return price * quantity * TAX_RATE;
}

// GOOD - Boolean naming
const isLoggedIn = true;
const hasPermission = user.role === 'admin';
const shouldShowModal = !hasSeenWelcome && isFirstVisit;

if (isLoggedIn && hasPermission) {
  // Do something
}

Consistent Formatting

Example - Formatted Code:

javascript
// Consistent indentation and spacing
function processOrder(order) {
  // Validate order
  if (!order.items || order.items.length === 0) {
    throw new Error('Order must contain items');
  }
  
  // Calculate total
  const subtotal = order.items.reduce((sum, item) => {
    return sum + (item.price * item.quantity);
  }, 0);
  
  const tax = subtotal * 0.08;
  const total = subtotal + tax;
  
  // Return result
  return {
    subtotal,
    tax,
    total,
    itemCount: order.items.length
  };
}

DRY Principle (Don't Repeat Yourself)

Example - Eliminating Repetition:

javascript
// WRONG - Repeated code
function getAdminEmail() {
  const user = getCurrentUser();
  if (user && user.role === 'admin') {
    return user.email;
  }
  return null;
}

function getAdminName() {
  const user = getCurrentUser();
  if (user && user.role === 'admin') {
    return user.name;
  }
  return null;
}

// CORRECT - Extract common logic
function getAdmin() {
  const user = getCurrentUser();
  if (user && user.role === 'admin') {
    return user;
  }
  return null;
}

function getAdminEmail() {
  const admin = getAdmin();
  return admin?.email ?? null;
}

function getAdminName() {
  const admin = getAdmin();
  return admin?.name ?? null;
}

Magic Numbers and Constants

Example - Using Constants:

javascript
// WRONG - Magic numbers
if (user.age >= 18 && user.age <= 65) {
  discount = price * 0.1;
}

setTimeout(() => {
  checkStatus();
}, 300000);

// CORRECT - Named constants
const MIN_ADULT_AGE = 18;
const RETIREMENT_AGE = 65;
const ADULT_DISCOUNT_RATE = 0.1;
const STATUS_CHECK_INTERVAL = 5 * 60 * 1000; // 5 minutes in milliseconds

if (user.age >= MIN_ADULT_AGE && user.age <= RETIREMENT_AGE) {
  discount = price * ADULT_DISCOUNT_RATE;
}

setTimeout(() => {
  checkStatus();
}, STATUS_CHECK_INTERVAL);

Destructuring for Clarity

Example - Clean Parameter Handling:

javascript
// WRONG - Accessing properties repeatedly
function displayUser(user) {
  console.log(user.name);
  console.log(user.email);
  console.log(user.address.city);
  console.log(user.address.country);
}

// CORRECT - Destructuring
function displayUser(user) {
  const { name, email, address } = user;
  const { city, country } = address;
  
  console.log(name);
  console.log(email);
  console.log(city);
  console.log(country);
}

// EVEN BETTER - Destructure in parameters
function displayUser({ name, email, address: { city, country } }) {
  console.log(name);
  console.log(email);
  console.log(city);
  console.log(country);
}

Frequently Asked Questions

Debugging in JavaScript is the process of finding and fixing errors in your code. It helps you understand why your program is not working as expected and how to correct mistakes using tools like the browser’s Developer Tools.

You can open Developer Tools by pressing F12 on Windows or Linux, or Command + Option + I on Mac. You can also right-click anywhere on a webpage and select Inspect.

console.log() allows beginners to see variable values and outputs while the code runs. It is the easiest way to understand program flow and detect mistakes.

Runtime errors appear while the program is running and usually stop execution. Logical errors do not show any error message but cause incorrect output.

The best way to improve debugging skills is regular practice. Use DevTools often, read error messages carefully, and debug small pieces of code step by step.