Debugging and Best Practices

10 minute read

What Is Debugging?

Debugging is the process of finding and fixing errors (bugs) in your code. Every developer—beginner or expert—spends time debugging. The key is knowing how to debug efficiently.

Types of Errors

1. Syntax Errors

Code that violates JavaScript’s grammar rules. These prevent code from running at all.

// ❌ Missing closing parenthesis
console.log("Hello";

// ❌ Missing closing bracket
let arr = [1, 2, 3;

// ❌ Misspelled keyword
funciton greet() {
    console.log("Hi");
}

// ❌ Missing quotes
let name = Alice;

How to fix: Check the error message—it usually points to the line with the problem.

2. Runtime Errors

Code that’s syntactically correct but fails during execution.

// ❌ Calling undefined function
greet();  // ReferenceError: greet is not defined

// ❌ Accessing property of undefined
let user;
console.log(user.name);  // TypeError: Cannot read property 'name' of undefined

// ❌ Dividing by zero (returns Infinity, not an error in JS)
let result = 10 / 0;
console.log(result);  // Infinity

3. Logical Errors

Code runs without errors, but produces incorrect results.

// ❌ Off-by-one error
for (let i = 0; i <= arr.length; i++) {  // Should be < not <=
    console.log(arr[i]);
}

// ❌ Wrong operator
if (age = 18) {  // Should be === not =
    console.log("You are 18");
}

// ❌ Wrong calculation
let discount = price * 0.1;
let finalPrice = price + discount;  // Should be - not +

Console Methods for Debugging

console.log() — Basic Output

let name = "Alice";
let age = 25;

console.log("Name:", name);
console.log("Age:", age);

// Multiple values
console.log("User:", name, "Age:", age);

// With template literals
console.log(`User: ${name}, Age: ${age}`);

console.error() — Error Messages

function divide(a, b) {
    if (b === 0) {
        console.error("Cannot divide by zero!");
        return;
    }
    return a / b;
}

console.warn() — Warnings

function deprecatedFunction() {
    console.warn("This function is deprecated. Use newFunction() instead.");
}

console.table() — Display Arrays/Objects

let users = [
    {name: "Alice", age: 25},
    {name: "Bob", age: 30},
    {name: "Carol", age: 28}
];

console.table(users);
// Displays a nicely formatted table

console.assert() — Conditional Logging

let age = 15;
console.assert(age >= 18, "User must be 18 or older");
// Only logs if condition is false

console.time() and console.timeEnd() — Performance

console.time("loop");

for (let i = 0; i < 1000000; i++) {
    // Some operation
}

console.timeEnd("loop");
// Output: loop: 5.234ms

console.clear() — Clear Console

console.clear();  // Clears all console output

Browser DevTools

Modern browsers include powerful debugging tools. Press F12 or Cmd+Option+I (Mac) / Ctrl+Shift+I (Windows) to open DevTools.

Console Tab

  • View console.log() output
  • See errors and warnings
  • Run JavaScript commands interactively
  • Access DOM elements

Try in console:

document.querySelector('body').style.backgroundColor = 'lightblue';

Sources Tab — Debugging Code

Set breakpoints:

  1. Open Sources tab
  2. Find your JavaScript file
  3. Click line number to set breakpoint
  4. Reload page—execution pauses at breakpoint

Controls:

  • Resume (F8): Continue execution
  • Step Over (F10): Execute current line, don’t enter functions
  • Step Into (F11): Enter function calls
  • Step Out (Shift+F11): Exit current function

Watch variables:

  • Right panel shows current variable values
  • Add watches to monitor specific variables

Elements Tab

  • Inspect HTML structure
  • View and modify CSS
  • See applied styles
  • Test style changes live

Network Tab

  • Monitor HTTP requests
  • Check API responses
  • Debug loading issues
  • View request/response headers

Debugging Techniques

1. Read Error Messages Carefully

// Error: Uncaught ReferenceError: greet is not defined
//        at script.js:10

// What it tells you:
// - Type: ReferenceError (variable/function doesn't exist)
// - Problem: greet is not defined
// - Location: script.js, line 10

2. Use console.log() Strategically

function calculateTotal(items) {
    console.log("calculateTotal called with:", items);
    
    let total = 0;
    
    for (let item of items) {
        console.log("Processing item:", item);
        total += item.price * item.quantity;
    }
    
    console.log("Final total:", total);
    return total;
}

3. Isolate the Problem

Comment out code to find where the error occurs:

// Code works fine
console.log("Part 1 works");

// Problematic code?
// functionThatMightFail();

// Does this run?
console.log("Did Part 2 run?");

4. Check Data Types

function add(a, b) {
    console.log("Type of a:", typeof a);
    console.log("Type of b:", typeof b);
    return a + b;
}

add("5", 3);  // Output: "53" (string concatenation, not addition!)

5. Validate Assumptions

let element = document.getElementById("myElement");
console.log("Element found?", element !== null);

if (element) {
    element.textContent = "Updated!";
} else {
    console.error("Element not found! Check ID spelling.");
}

6. Use debugger Statement

Forces JavaScript to pause execution (if DevTools are open).

function complexFunction(data) {
    debugger;  // Execution pauses here
    
    // Inspect variables in DevTools
    let result = data.map(item => item * 2);
    return result;
}

Common Errors and Solutions

Error 1: ReferenceError

Problem: Variable or function doesn’t exist.

// ❌ Error
console.log(userName);  // ReferenceError: userName is not defined

// ✅ Solution: Declare the variable
let userName = "Alice";
console.log(userName);

Error 2: TypeError

Problem: Trying to use wrong type or access property of null/undefined.

// ❌ Error
let user = null;
console.log(user.name);  // TypeError: Cannot read property 'name' of null

// ✅ Solution: Check before accessing
if (user) {
    console.log(user.name);
} else {
    console.log("User is null");
}

// ✅ Optional chaining (modern)
console.log(user?.name);  // undefined (no error)

Error 3: Undefined Is Not a Function

Problem: Trying to call something that isn’t a function.

// ❌ Error
let calculate = 5;
calculate();  // TypeError: calculate is not a function

// ✅ Solution: Ensure it's a function
let calculate = function() {
    return 5;
};
calculate();  // Works

Error 4: Cannot Read Property of Undefined

Problem: Accessing property before object is set.

// ❌ Error
let user;
console.log(user.name);  // TypeError

// ✅ Solution: Initialize properly
let user = {name: "Alice"};
console.log(user.name);  // Works

Error 5: Unexpected Token

Problem: Syntax error (missing bracket, comma, quote).

// ❌ Error
let obj = {
    name: "Alice"
    age: 25  // Missing comma!
};

// ✅ Solution
let obj = {
    name: "Alice",
    age: 25
};

JavaScript Best Practices

1. Use Strict Mode

Catches common mistakes and prevents bad practices.

"use strict";

// This would fail in strict mode
undeclaredVariable = 10;  // ReferenceError

2. Variable Naming Conventions

// ✅ Good: Descriptive, camelCase
let firstName = "Alice";
let totalPrice = 99.99;
let isLoggedIn = true;

// ❌ Bad: Unclear, abbreviations
let fn = "Alice";
let tp = 99.99;
let li = true;

// Constants: UPPERCASE_WITH_UNDERSCORES
const MAX_USERS = 100;
const API_BASE_URL = "https://api.example.com";

3. Use const and let, Never var

// ✅ Good
const PI = 3.14159;
let counter = 0;

// ❌ Bad (outdated)
var x = 10;

4. Always Use Semicolons

// ✅ Good
let name = "Alice";
console.log(name);

// ❌ Can cause issues (ASI is unpredictable)
let name = "Alice"
console.log(name)

5. Use === Instead of ==

// ❌ Loose equality (type coercion)
console.log(5 == "5");   // true (unexpected!)
console.log(0 == false); // true (unexpected!)

// ✅ Strict equality
console.log(5 === "5");   // false (correct!)
console.log(0 === false); // false (correct!)

6. Keep Functions Small and Focused

// ❌ Bad: Function does too much
function processUserData(user) {
    // Validate user
    // Update database
    // Send email
    // Update UI
    // Log activity
}

// ✅ Good: Single responsibility
function validateUser(user) { ... }
function updateDatabase(user) { ... }
function sendEmail(user) { ... }
function updateUI(user) { ... }
function logActivity(action) { ... }

7. Use Meaningful Names

// ❌ Bad
function f(x, y) {
    return x * y * 0.08;
}

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

8. Add Comments for Complex Logic

// Calculate compound interest
// Formula: A = P(1 + r)^t
function calculateCompoundInterest(principal, rate, time) {
    return principal * Math.pow(1 + rate, time);
}

// ❌ Don't comment obvious code
let x = 5;  // Set x to 5 (redundant!)

// ✅ Explain WHY, not what
let retryDelay = 5000;  // Wait 5 seconds to avoid rate limiting

9. Handle Errors Gracefully

// ✅ Check for potential errors
function divide(a, b) {
    if (b === 0) {
        console.error("Cannot divide by zero");
        return null;
    }
    return a / b;
}

// ✅ Try-catch for external operations
async function fetchData(url) {
    try {
        const response = await fetch(url);
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Failed to fetch data:", error);
        return null;
    }
}

10. Don’t Repeat Yourself (DRY)

// ❌ Bad: Repeated code
let user1Total = user1.items * user1.price * 1.08;
let user2Total = user2.items * user2.price * 1.08;
let user3Total = user3.items * user3.price * 1.08;

// ✅ Good: Reusable function
function calculateTotal(user) {
    const TAX_RATE = 1.08;
    return user.items * user.price * TAX_RATE;
}

let user1Total = calculateTotal(user1);
let user2Total = calculateTotal(user2);
let user3Total = calculateTotal(user3);

11. Avoid Global Variables

// ❌ Bad: Pollutes global scope
var globalCounter = 0;

function increment() {
    globalCounter++;
}

// ✅ Good: Use modules or closures
function createCounter() {
    let count = 0;
    
    return {
        increment() {
            count++;
        },
        getCount() {
            return count;
        }
    };
}

const counter = createCounter();

12. Use Template Literals

// ❌ Bad: String concatenation
let message = "Hello, " + name + "! You have " + count + " messages.";

// ✅ Good: Template literals
let message = `Hello, ${name}! You have ${count} messages.`;

Performance Best Practices

1. Cache DOM Selections

// ❌ Bad: Queries DOM repeatedly
for (let i = 0; i < 100; i++) {
    document.getElementById("output").textContent = i;
}

// ✅ Good: Cache the element
let output = document.getElementById("output");
for (let i = 0; i < 100; i++) {
    output.textContent = i;
}

2. Minimize DOM Manipulation

// ❌ Bad: Many small DOM changes
let list = document.getElementById("list");
for (let i = 0; i < 100; i++) {
    let li = document.createElement("li");
    li.textContent = `Item ${i}`;
    list.appendChild(li);  // Reflows DOM 100 times!
}

// ✅ Good: Build string, insert once
let html = "";
for (let i = 0; i < 100; i++) {
    html += `<li>Item ${i}</li>`;
}
list.innerHTML = html;  // Single DOM update

3. Use Event Delegation

// ❌ Bad: Listener on each item
items.forEach(item => {
    item.addEventListener("click", handleClick);
});

// ✅ Good: Single listener on parent
parent.addEventListener("click", (e) => {
    if (e.target.classList.contains("item")) {
        handleClick(e);
    }
});

Code Organization

// ✅ Good: Organized sections
// ========== Configuration ==========
const API_URL = "https://api.example.com";
const TIMEOUT = 5000;

// ========== Helper Functions ==========
function formatDate(date) { ... }
function validateEmail(email) { ... }

// ========== Main Logic ==========
function init() { ... }
function loadData() { ... }

2. Use Modules

// utils.js
export function formatCurrency(amount) {
    return `$${amount.toFixed(2)}`;
}

// main.js
import { formatCurrency } from './utils.js';
console.log(formatCurrency(19.99));

Next Steps in Your JavaScript Journey

Congratulations on completing JavaScript Fundamentals! Here’s what to learn next:

Immediate Next Steps

  1. Build more projects: Practice is key!
  2. Learn ES6+ features: Destructuring, spread operator, async/await
  3. Master Array methods: map, filter, reduce, etc.
  4. Study the DOM deeply: Events, performance, accessibility

Intermediate Level

  1. Asynchronous JavaScript: Promises, async/await, fetch API
  2. JavaScript Frameworks: React, Vue, or Angular
  3. Node.js: Backend JavaScript
  4. Module bundlers: Webpack, Vite
  5. Version control: Git and GitHub

Advanced Topics

  1. Design patterns: Module, Observer, Factory, etc.
  2. Testing: Jest, Mocha, unit tests
  3. TypeScript: Typed JavaScript
  4. Performance optimization: Debouncing, throttling, memoization
  5. Web APIs: localStorage, geolocation, notifications

Resources for Continued Learning

Documentation

Practice Platforms

Community

  • Stack Overflow: Ask questions
  • Reddit r/learnjavascript: Community support
  • Discord/Slack communities: Real-time help
  • Local meetups: Network with developers

Key Takeaways

✅ Debugging is a skill—practice makes perfect
✅ Use browser DevTools for efficient debugging
✅ Read error messages carefully—they guide you to the problem
✅ console.log() is your friend—use it liberally
✅ Always use const and let, never var
✅ Use === instead of == for comparisons
✅ Write small, focused functions with single responsibilities
✅ Add comments for complex logic, not obvious code
✅ Handle errors gracefully with validation and try-catch
✅ Optimize performance by caching DOM queries and minimizing manipulation
✅ Keep learning—JavaScript evolves constantly!

Final Words

You’ve completed JavaScript Fundamentals! You now have a solid foundation in:

  • Variables, data types, and operators
  • Control flow and loops
  • Functions and scope
  • Arrays and objects
  • DOM manipulation
  • Event handling
  • Debugging techniques
  • Best practices

Most importantly: You’ve built a complete project and know how to apply these concepts in real applications.

Keep building, keep learning, and keep coding. The JavaScript journey has just begun!