JS

Introduction To JS

Content Overview

  1. Complete JavaScript Learning Roadmap
    1. Introduction
      1. 1.1 What is JavaScript
      2. 1.2 History and Evolution
      3. 1.3 ECMAScript Standards
      4. 1.4 Browser Engines
      5. 1.5 JavaScript Runtime Environments
      6. 1.6 JavaScript Implementation (In-page, External)
    2. Detailed Setup and First Application
      1. 2.1 Prerequisites for Environment Setup
      2. 2.2 Linux Command-Line Environment
      3. 2.3 Linux Professional IDE Environment
      4. 2.4 Linux AI-Integrated Workflow
      5. 2.5 Windows Command-Line Environment
      6. 2.6 Windows Professional IDE Environment
      7. 2.7 Windows AI-Integrated Workflow
      8. 2.8 macOS Command-Line Environment
      9. 2.9 macOS Professional IDE Environment
      10. 2.10 macOS AI-Integrated Workflow
      11. 2.11 Software Execution Lifecycle
    3. AI Integration with Development
      1. 3.1 AI-Assisted Learning
      2. 3.2 AI Debugging
      3. 3.3 AI Code Reviews
      4. 3.4 AI Refactoring
      5. 3.5 AI Testing Assistance
      6. 3.6 AI Documentation Generation
    4. JavaScript Foundations
      1. 4.1 Client-Side Scripting
      2. 4.2 Server-Side JavaScript
      3. 4.3 Event-Driven Programming
      4. 4.4 Execution Context
      5. 4.5 Call Stack
      6. 4.6 Event Loop
      7. 4.7 Closures
      8. 4.8 Scope Chain
      9. 4.9 Hoisting
    5. Core JavaScript
      1. 5.1 Variables (var, let, const, Temporal Dead Zone)
      2. 5.2 Data Types (Primitive vs Reference)
      3. 5.3 Operators
      4. 5.4 Conditionals
      5. 5.5 Loops
      6. 5.6 Functions
      7. 5.7 Arrays
      8. 5.8 Objects
      9. 5.9 Type Conversion & Coercion, Strict Mode, Equality
      10. 5.10 Error Handling
      11. 5.11 Regular Expressions
      12. 5.12 Memory (Stack vs Heap, Garbage Collection)
    6. Object-Oriented Programming
      1. 6.1 Encapsulation
      2. 6.2 Abstraction
      3. 6.3 Inheritance
      4. 6.4 Polymorphism
      5. 6.5 Constructor Functions (pre-ES6)
      6. 6.6 Prototypes
      7. 6.7 Prototype Chain
      8. 6.8 ES6 Classes
      9. 6.9 Static Methods & Properties
      10. 6.10 Private Fields (#)
      11. 6.11 Object.create()
      12. 6.12 this Keyword
    7. Advanced JavaScript
      1. 7.1 Promises
      2. 7.2 Async/Await
      3. 7.3 Error Handling (try/catch/finally)
      4. 7.4 Fetch API / Axios
      5. 7.5 Generators & Iterators
      6. 7.6 Destructuring
      7. 7.7 Spread & Rest Operators
      8. 7.8 Optional Chaining & Nullish Coalescing
      9. 7.9 Modules (ESM vs CommonJS)
      10. 7.10 Template Literals, Default Parameters, Arrow Functions
      11. 7.11 Event Loop Deep Dive (Microtask vs Macrotask)
      12. 7.12 Web Workers & Service Workers
      13. 7.13 WebSockets
      14. 7.14 Debouncing / Throttling
      15. 7.15 Performance Optimization
    8. TypeScript & Lightweight Libraries
      1. 8.1 Static Typing
      2. 8.2 Interfaces
      3. 8.3 Enums
      4. 8.4 Generics
      5. 8.5 Type Guards
      6. 8.6 Type Inference
      7. 8.7 TS with React
      8. 8.8 Lightweight Libraries (Stimulus, Alpine.js, HTMX)
    9. Frontend Libraries
      1. 9.1 React
      2. 9.2 Vue.js
      3. 9.3 jQuery
    10. Backend Development
      1. 11.1 Node.js
      2. 11.2 Express.js
      3. 11.3 REST APIs
    11. Testing
      1. 12.1 Jest
    12. Build Tools
      1. 13.1 Webpack
      2. 13.5 Vite
    13. Career Readiness
      1. 20.1 Portfolio Projects
    14. 📊 API Endpoints
    15. 🧪 Testing
    16. 🐳 Docker
    17. Final Advice

Complete JavaScript Learning Roadmap

Introduction

1.1 What is JavaScript

JavaScript is a high-level, dynamic, interpreted programming language that conforms to the ECMAScript specification. It is primarily known for adding interactivity to web pages but has grown into a full-stack language via Node.js. JavaScript is single-threaded, event-driven, and supports functional, object-oriented, and prototype-based programming styles.

Key Features of JavaScript:

  • High-level – Abstracted from hardware details
  • Dynamic – Types are determined at runtime
  • Interpreted – No compilation step needed
  • Event-driven – Responds to user actions and events
  • Prototype-based – Inheritance through prototypes
  • Functional – Functions as first-class citizens

Code Example:

console.log("Hello, JavaScript!");
// Output: Hello, JavaScript!

The console.log() function outputs text to the browser’s developer console or terminal. JavaScript code is executed by a JavaScript engine (like V8 in Chrome or Node.js). The // syntax creates a single-line comment that is ignored by the engine.

1.2 History and Evolution

JavaScript was created by Brendan Eich at Netscape in 1995. Originally named Mocha, then LiveScript, it became JavaScript to ride the wave of Java’s popularity.

Key Milestones:

  • 1995 – JavaScript created in 10 days by Brendan Eich
  • 1996 – Submitted to ECMA International for standardization, leading to ECMAScript
  • 2005 – AJAX popularized asynchronous web apps
  • 2008 – Google’s V8 engine dramatically improves performance
  • 2009 – Node.js created, enabling server-side JavaScript
  • 2015 – ES6/ES2015 introduces let/const, classes, arrow functions, modules, promises
  • 2020 – Optional chaining and nullish coalescing added
  • 2022 – Top-level await and class fields

Code Example:

// ES6+ feature that shows evolution
const greet = (name) => `Hello, ${name}!`;
console.log(greet("World"));
// Output: Hello, World!

The const keyword declares a constant variable. The arrow function (name) => ... is a concise function syntax. Template literals use backticks and ${} for string interpolation. This demonstrates modern JavaScript features.

1.3 ECMAScript Standards

ECMAScript (ES) is the standardized specification that JavaScript implements. Each year a new edition is released.

Major ECMAScript Versions:

VersionYearKey Features
ES52009Strict mode, JSON, array helpers
ES6/ES20152015let/const, classes, arrow functions, modules, promises
ES20162016Array.prototype.includes, exponentiation operator
ES20172017async/await, Object.values, Object.entries
ES20182018Rest/spread for objects, asynchronous iteration
ES20192019Array.prototype.flat, Object.fromEntries
ES20202020Optional chaining (?.), nullish coalescing (??)
ES20212021String.prototype.replaceAll, logical assignment operators
ES20222022Class fields, top-level await, Array.prototype.at

Code Example:

// ES2020 features - optional chaining and nullish coalescing
const user = { profile: { name: "Alice" } };
console.log(user.profile?.age ?? 18); // 18
console.log(user?.profile?.address?.city ?? "Unknown"); // Unknown

Optional chaining ?. safely accesses nested properties without throwing errors if a property is null/undefined. Nullish coalescing ?? provides a default value only when the left side is null or undefined (not for false or 0).

1.4 Browser Engines

Browser engines interpret and execute JavaScript, providing the runtime environment for web applications.

Major Browser Engines:

EngineBrowserPlatform
V8Chrome, Edge, Node.jsCross-platform
SpiderMonkeyFirefoxCross-platform
JavaScriptCore (Nitro)SafarimacOS, iOS
Chakra (retired)Old EdgeWindows

Code Example:

// This code runs inside a browser engine
document.querySelector("button")?.addEventListener("click", () => {
    console.log("Button clicked");
});

The document object is provided by the browser engine. querySelector() finds the first matching button element. addEventListener() registers a click handler. The arrow function executes when the button is clicked. The optional chaining prevents errors if the button doesn’t exist.

1.5 JavaScript Runtime Environments

A runtime environment provides the JavaScript engine with additional APIs for specific use cases.

Runtime Environments:

EnvironmentAPIs ProvidedUse Case
Browserdocument, window, fetch, localStorageWeb applications
Node.jsfs, http, process, BufferServer-side applications
DenoSecure runtime with TypeScriptModern server applications
BunFast all-in-one runtimeHigh-performance applications

Code Example (Node.js):

// Node.js file system module
const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
});

In Node.js, the require() function imports modules. fs.readFile() reads a file asynchronously. The callback receives error and data parameters. If there’s no error, the file content is logged to the console.

1.6 JavaScript Implementation (In-page, External)

JavaScript can be added to HTML in two main ways: inline scripts within HTML files, and external script files.

In-page (Inline) Script:

<script>
    console.log("Inline JavaScript");
</script>

External JavaScript File:

<script src="app.js"></script>
// app.js
console.log("External JavaScript");

Benefits of External JavaScript:

  • Separation of concerns – HTML, CSS, and JS are separate
  • Caching – Browser caches external scripts
  • Reusability – Same script can be used on multiple pages
  • Maintainability – Easier to update and debug

The <script> tag tells the browser to load and execute JavaScript. For inline scripts, the code runs immediately. For external scripts, the browser downloads the file and executes it. Scripts are executed in the order they appear in the HTML.

Detailed Setup and First Application

2.1 Prerequisites for Environment Setup

Before starting JavaScript development, ensure your system meets the requirements.

System Requirements:

  • OS: Ubuntu 24.04 LTS or newer / Windows 10/11 / macOS Ventura or newer
  • RAM: 8 GB minimum (16 GB recommended)
  • Storage: 20 GB free space
  • Internet: Stable connection for downloads
  • Node.js: Latest LTS version
  • Editor: VS Code or any text editor
  • Browser: Chrome, Firefox, or Edge

Code Example:

// Test your Node.js setup
console.log("Node.js is working!");
console.log("Node version:", process.version);
console.log("Platform:", process.platform);

This program checks your Node.js environment. process.version returns the Node.js version. process.platform returns the operating system platform (win32, linux, darwin, etc.).

2.2 Linux Command-Line Environment

Setting up JavaScript development on Linux using the terminal.

Step 1: Update Packages

sudo apt update

Step 2: Install Node.js

sudo apt install nodejs npm -y

Step 3: Verify Installation

node -v   # Should show v22.x.x or similar
npm -v    # Should show 10.x.x or similar

Step 4: Create Project Folder

mkdir js-course
cd js-course

Step 5: Create Source File

echo 'console.log("Hello Linux");' > app.js

Step 6: Run the Program

node app.js

Expected Output:

Hello Linux

Code Example:

// More advanced example with user input
const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.question('Enter your name: ', (name) => {
    console.log(`Hello, ${name}! Welcome to JavaScript on Linux.`);
    rl.close();
});

The readline module handles user input in Node.js. createInterface() creates a read/write interface. question() prompts the user and waits for input. The callback executes when the user presses Enter.

2.3 Linux Professional IDE Environment

Setting up VS Code for professional JavaScript development on Linux.

Step 1: Install VS Code

sudo snap install --classic code

Step 2: Launch VS Code

code .

Step 3: Install Extensions

Open Extensions panel (Ctrl+Shift+X) and install:

  • Prettier – Code formatter
  • ESLint – JavaScript linting
  • JavaScript (ES6) code snippets

Step 4: Create Project

  1. Create a new folder: mkdir my-app && cd my-app
  2. Open VS Code: code .
  3. Create app.js
  4. Write code and run with node app.js

Code Example:

// Using VS Code features
const birds = ["Eagle", "Sparrow", "Hawk", "Cardinal"];

// Use auto-completion and IntelliSense
const flyingBirds = birds
    .filter(bird => bird.length > 5)
    .map(bird => bird.toUpperCase())
    .sort();

console.log("Birds:", flyingBirds);

VS Code provides autocompletion and IntelliSense. filter() creates a new array with elements matching the condition. map() transforms each element. sort() arranges elements in order. The IDE helps with syntax and function suggestions.

2.4 Linux AI-Integrated Workflow

Integrating AI tools like GitHub Copilot with VS Code on Linux.

Step 1: Install GitHub Copilot

  1. Open VS Code
  2. Go to Extensions (Ctrl+Shift+X)
  3. Search for “GitHub Copilot”
  4. Click “Install”
  5. Restart VS Code

Step 2: Sign In

  1. Go to the command palette (Ctrl+Shift+P)
  2. Type “GitHub Copilot: Sign in”
  3. Follow browser prompts

Step 3: Use Copilot

Start typing and Copilot will suggest completions.

Code Example:

// Start typing and Copilot will suggest code
// Type: "function to calculate bird average weight"
// Copilot generates:
function calculateAverageWeight(weights) {
    if (!weights || weights.length === 0) return 0;
    const sum = weights.reduce((acc, weight) => acc + weight, 0);
    return sum / weights.length;
}

const birdWeights = [25.5, 4500.0, 1200.0, 45.0];
console.log("Average weight:", calculateAverageWeight(birdWeights).toFixed(2));

AI Integration Example:

// Ask AI: "Write a function to count bird occurrences"
// AI generates:
function countBirdOccurrences(birds) {
    const counts = {};
    for (const bird of birds) {
        counts[bird] = (counts[bird] || 0) + 1;
    }
    return counts;
}

const birdList = ["Sparrow", "Eagle", "Hawk", "Sparrow", "Eagle", "Sparrow"];
const counts = countBirdOccurrences(birdList);
console.log("Bird counts:", counts);
// Output: Bird counts: { Sparrow: 3, Eagle: 2, Hawk: 1 }

AI tools analyze your code context and suggest completions. reduce() accumulates values. toFixed() formats decimal places. The AI generates entire functions based on comments or partial code.

2.5 Windows Command-Line Environment

Setting up JavaScript development on Windows using Command Prompt/PowerShell.

Step 1: Download Node.js

  1. Go to https://nodejs.org/
  2. Download the LTS version (Windows installer)
  3. Run the installer

Step 2: Verify Installation

node -v
npm -v

Step 3: Create Project Folder

mkdir js-course
cd js-course

Step 4: Create Source File

echo console.log("Hello Windows"); > app.js

Step 5: Run the Program

node app.js

Code Example:

// Windows-specific example with file system
const fs = require('fs');

// Get current directory
const currentDir = process.cwd();
console.log("Current directory:", currentDir);

// List files
fs.readdir('.', (err, files) => {
    if (err) throw err;
    console.log("Files in current directory:");
    files.forEach(file => console.log("  -", file));
});

process.cwd() returns the current working directory. fs.readdir() reads directory contents asynchronously. The forEach() method processes each item in the file list one at a time.

2.6 Windows Professional IDE Environment

Setting up VS Code on Windows.

Step 1: Download VS Code

  1. Go to https://code.visualstudio.com/
  2. Click “Download for Windows”
  3. Run the installer

Step 2: Install Extensions

  1. Open Extensions panel (Ctrl+Shift+X)
  2. Install Prettier, ESLint, and JavaScript snippets

Step 3: Create Project

  1. Open Command Prompt
  2. Create project folder: mkdir my-app && cd my-app
  3. Open VS Code: code .
  4. Create app.js
  5. Write and run code

Code Example:

// More complex example with classes
class BirdTracker {
    constructor() {
        this.birds = new Map();
    }
    
    addBird(species, count) {
        const current = this.birds.get(species) || 0;
        this.birds.set(species, current + count);
        console.log(`Added ${count} ${species} birds.`);
    }
    
    getTotal() {
        let total = 0;
        for (const count of this.birds.values()) {
            total += count;
        }
        return total;
    }
    
    display() {
        console.log("Bird counts:");
        for (const [species, count] of this.birds) {
            console.log(`  ${species}: ${count}`);
        }
    }
}

// Usage
const tracker = new BirdTracker();
tracker.addBird("Sparrow", 10);
tracker.addBird("Eagle", 3);
tracker.addBird("Sparrow", 5);
tracker.display();
console.log("Total birds:", tracker.getTotal());

Map stores key-value pairs with any data type as keys. The for...of loop goes through the elements of an iterable object one by one, allowing each value to be processed in sequence. The spread operator ... expands arrays. Class methods encapsulate behavior.

2.7 Windows AI-Integrated Workflow

Using AI tools in VS Code on Windows.

Step 1: Install GitHub Copilot

  1. File → Preferences → Extensions
  2. Search “GitHub Copilot”
  3. Install and restart

Step 2: Sign In

Tools → GitHub Copilot → Login

Step 3: Use AI Features

Start typing and Copilot suggests completions.

Code Example:

// AI-assisted coding example
// Type: "function to find birds with count above threshold"
// Copilot generates:
function findBirdsAboveThreshold(birdMap, threshold) {
    const result = [];
    for (const [species, count] of birdMap) {
        if (count > threshold) {
            result.push(species);
        }
    }
    return result;
}

const birdCounts = new Map([
    ["Sparrow", 10],
    ["Eagle", 3],
    ["Hawk", 5],
    ["Cardinal", 8]
]);

const aboveFive = findBirdsAboveThreshold(birdCounts, 5);
console.log("Birds with more than 5 sightings:", aboveFive);

AI Integration Example:

// Ask AI: "Generate a function to filter birds by weight"
// AI generates:
function filterBirdsByWeight(birds, minWeight, maxWeight) {
    return birds.filter(bird => 
        bird.weight >= minWeight && bird.weight <= maxWeight
    );
}

const birds = [
    { species: "Sparrow", weight: 25.5 },
    { species: "Eagle", weight: 4500.0 },
    { species: "Hawk", weight: 1200.0 },
    { species: "Cardinal", weight: 45.0 }
];

const mediumBirds = filterBirdsByWeight(birds, 100, 1000);
console.log("Medium birds:", mediumBirds.map(b => b.species));
// Output: Medium birds: [ 'Hawk' ]

AI coding tools can recognize common JavaScript patterns and idioms and suggest code that follows them. The filter() method builds a new array containing only the elements that meet a specified condition, while map() produces a new array by applying a transformation to each element. The AI generates functions based on context and comments.

2.8 macOS Command-Line Environment

Setting up JavaScript development on macOS using Terminal.

Step 1: Install Homebrew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Step 2: Install Node.js

brew install node

Step 3: Verify Installation

node -v
npm -v

Step 4: Create Project Folder

mkdir ~/js-course
cd ~/js-course

Step 5: Create Source File

echo 'console.log("Hello macOS");' > app.js

Step 6: Run the Program

node app.js

Code Example:

// macOS-specific example with system information
const os = require('os');

console.log("System Information:");
console.log("OS:", os.platform());
console.log("Release:", os.release());
console.log("Hostname:", os.hostname());
console.log("CPUs:", os.cpus().length);
console.log("Total Memory:", (os.totalmem() / 1024 / 1024 / 1024).toFixed(2), "GB");
console.log("Free Memory:", (os.freemem() / 1024 / 1024 / 1024).toFixed(2), "GB");

The os module provides operating system information. os.platform() returns the platform (darwin for macOS). os.cpus() returns CPU information. os.totalmem() and os.freemem() return memory information in bytes.

2.9 macOS Professional IDE Environment

Setting up VS Code on macOS.

Step 1: Install VS Code

brew install --cask visual-studio-code

Step 2: Launch VS Code

Open from Applications folder

Step 3: Install Extensions

  1. Open Extensions panel (Cmd+Shift+X)
  2. Install Prettier, ESLint, and JavaScript snippets

Step 4: Create Project

  1. Create folder: mkdir my-app && cd my-app
  2. Open VS Code: code .
  3. Create app.js
  4. Write and run code

Code Example:

// Advanced JavaScript with ES modules
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';

// Get current file path
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Async function to read and process files
async function processDirectory(dirPath) {
    try {
        const files = await fs.readdir(dirPath);
        const jsFiles = files.filter(file => file.endsWith('.js'));
        console.log("JavaScript files:", jsFiles);
        
        for (const file of jsFiles) {
            const filePath = path.join(dirPath, file);
            const stats = await fs.stat(filePath);
            console.log(`${file}: ${(stats.size / 1024).toFixed(2)} KB`);
        }
    } catch (error) {
        console.error("Error:", error.message);
    }
}

processDirectory(__dirname);

ES modules use import/export syntax. fs/promises provides Promise-based file operations. fileURLToPath converts file URLs to paths. path.join creates cross-platform file paths. import.meta.url provides the current module URL.

2.10 macOS AI-Integrated Workflow

Integrating AI tools in VS Code on macOS.

Step 1: Install GitHub Copilot

  1. VS Code → Extensions → Search “GitHub Copilot”
  2. Install and restart

Step 2: Sign In

View → Command Palette (Cmd+Shift+P) → “GitHub Copilot: Sign in”

Step 3: Use AI Features

Start typing and Copilot suggests completions.

Code Example:

// AI-assisted functional programming
// Type: "function to sum even numbers"
// Copilot generates:
function sumEvenNumbers(numbers) {
    return numbers
        .filter(num => num % 2 === 0)
        .reduce((sum, num) => sum + num, 0);
}

const birdCounts = [10, 3, 5, 8, 12, 6];
const evenTotal = sumEvenNumbers(birdCounts);
console.log("Sum of even counts:", evenTotal);

// AI suggests functional transformations
const processed = birdCounts
    .filter(count => count > 5)
    .map(count => count * 2)
    .sort((a, b) => b - a);

console.log("Processed:", processed);

AI Integration Example:

// Ask AI: "Write a function to find birds with counts above threshold"
// AI generates:
function findBirdsAboveThreshold(birdMap, threshold) {
    return Object.entries(birdMap)
        .filter(([species, count]) => count > threshold)
        .map(([species]) => species);
}

const birdData = {
    Sparrow: 10,
    Eagle: 3,
    Hawk: 5,
    Cardinal: 8,
    Finch: 12
};

console.log("Birds with >5 sightings:", findBirdsAboveThreshold(birdData, 5));
// Output: Birds with >5 sightings: [ 'Sparrow', 'Cardinal', 'Finch' ]

AI tools understand functional programming patterns. Object.entries() converts objects to arrays of key-value pairs. filter() creates a new array with matching elements. map() extracts values. Chain methods for readable, concise code.

2.11 Software Execution Lifecycle

Understanding the journey of JavaScript code from source to execution helps explain how a program is processed and eventually run by the JavaScript engine.

Execution Lifecycle Steps:

  1. Write source code – Create .js files with JavaScript code
  2. Save file – Save changes to disk
  3. JavaScript engine reads code – Engine loads the source code
  4. Parsing into AST – Code is parsed into an Abstract Syntax Tree
  5. Compilation (JIT) – Just-In-Time compilation to bytecode
  6. Execution context created – Global execution context is established
  7. Code runs on call stack – Functions are pushed and popped from the stack
  8. Event loop handles async – Asynchronous callbacks are processed
  9. Debugging – Debug using console, breakpoints, and dev tools
  10. Optimization – Bundle, minify, and optimize code
  11. Deployment – Deploy to production environment

Code Example:

// Demonstrating the execution lifecycle
console.log("Start");

// This will be handled by the event loop
setTimeout(() => console.log("Async"), 0);

console.log("End");

// Output: Start, End, Async

The synchronous code runs first (console.log("Start") and console.log("End")). setTimeout() is handed off to the browser/Node.js APIs. The callback function is added to the task queue, where it waits until the JavaScript runtime is ready to execute it. The event loop moves the callback to the call stack when the stack is empty. This demonstrates the event-driven nature of JavaScript.

AI Integration with Development

3.1 AI-Assisted Learning

AI can explain concepts, generate examples, and create exercises. Use it to understand closures, hoisting, or the event loop.

Key Use Cases:

  • Explain concepts – Get plain English explanations
  • Generate examples – Request specific code examples
  • Create exercises – Get practice problems
  • Debug code – Find and fix bugs
  • Review code – Get feedback on your code

Code Example:

// Example AI might generate to explain closure
function outer() {
    let count = 0;
    return function inner() {
        count++;
        return count;
    };
}

const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

The outer() function returns the inner() function. The inner() function has access to the count variable even after outer() has finished executing. This is a closure – a function that remembers its outer variables.

3.2 AI Debugging

AI can identify errors in your code and suggest fixes.

Common Debugging Scenarios:

Error TypeCauseFix
ReferenceErrorVariable not definedDeclare variable before use
TypeErrorAccessing property on null/undefinedCheck for null/undefined first
SyntaxErrorInvalid code syntaxFix syntax issues
RangeErrorValue out of rangeCheck bounds before using

Code Example (Before and After):

// ---- BUGGY CODE ----
// console.log(x); // ReferenceError: x is not defined

// ---- FIXED CODE ----
let x = 10;
console.log(x); // 10

// ---- BUGGY CODE (type error) ----
// const user = null;
// console.log(user.name); // TypeError: Cannot read property 'name' of null

// ---- FIXED CODE ----
const user = null;
console.log(user?.name ?? "Guest"); // Guest

ReferenceError occurs when using an undeclared variable. Use let, const, or var to declare variables. TypeError occurs when accessing properties of null/undefined. Use optional chaining ?. to safely access nested properties.

3.3 AI Code Reviews

AI can review your code for readability, performance, and best practices.

Code Example:

// ---- ORIGINAL (Imperative) ----
function getAdultNames(users) {
    const result = [];
    for (let i = 0; i < users.length; i++) {
        if (users[i].age >= 18) {
            result.push(users[i].name);
        }
    }
    return result;
}

// ---- AI REVIEW: Improved Functional Version ----
function getAdultNames(users) {
    return users
        .filter(user => user.age >= 18)
        .map(user => user.name);
}

// ---- ORIGINAL (Redundant Check) ----
function greet(name) {
    if (name !== undefined && name !== null && name !== "") {
        return `Hello, ${name}!`;
    }
    return "Hello, Guest!";
}

// ---- AI REVIEW: Use Nullish Coalescing ----
function greet(name) {
    return `Hello, ${name ?? "Guest"}!`;
}

AI code review identifies redundant loops and suggests functional alternatives. filter() creates a new array with elements matching the predicate. map() transforms each element. Nullish coalescing ?? provides a default value for null/undefined.

3.4 AI Refactoring

AI can transform imperative code into functional style or break large functions into smaller ones.

Common Refactoring Patterns:

  • Loop → Functional chain (filter, map, reduce)
  • if-else → Ternary operator or switch
  • Function extraction – Break large functions into smaller ones
  • Constant extraction – Replace magic numbers with constants
  • Destructuring – Extract object properties into variables

Code Example:

// ---- ORIGINAL ----
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let evens = [];
for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 === 0) {
        evens.push(numbers[i]);
    }
}
let doubled = [];
for (let i = 0; i < evens.length; i++) {
    doubled.push(evens[i] * 2);
}
console.log(doubled); // [4, 8, 12, 16, 20]

// ---- REFACTORED ----
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const doubled = numbers
    .filter(n => n % 2 === 0)
    .map(n => n * 2);
console.log(doubled); // [4, 8, 12, 16, 20]

AI refactoring converts imperative code to functional style. filter() selects even numbers. map() doubles each number. The chain is concise and readable. Functional programming reduces side effects and makes code more predictable.

3.5 AI Testing Assistance

AI can generate unit tests using Jest or Mocha.

Code Example:

// ---- FUNCTION TO TEST ----
function calculateTotal(price, quantity, discount = 0) {
    if (price <= 0 || quantity <= 0) {
        throw new Error("Price and quantity must be positive");
    }
    const subtotal = price * quantity;
    const discountAmount = subtotal * (discount / 100);
    return subtotal - discountAmount;
}

// ---- AI GENERATED TESTS (Jest) ----
describe('calculateTotal', () => {
    test('calculates total with no discount', () => {
        expect(calculateTotal(10, 5)).toBe(50);
    });
    
    test('calculates total with discount', () => {
        expect(calculateTotal(10, 5, 20)).toBe(40);
    });
    
    test('throws error for invalid price', () => {
        expect(() => calculateTotal(-5, 5)).toThrow('Price and quantity must be positive');
    });
    
    test('throws error for invalid quantity', () => {
        expect(() => calculateTotal(10, -5)).toThrow('Price and quantity must be positive');
    });
});

AI generates comprehensive tests covering normal cases, edge cases, and error scenarios. describe groups related tests. test (or it) defines individual test cases. expect makes assertions. toThrow verifies error throwing.

3.6 AI Documentation Generation

AI can generate JSDoc comments for functions.

Code Example:

// ---- AI GENERATED DOCUMENTATION ----
/**
 * Calculates the total price including discount.
 * 
 * @param {number} price - The unit price of the item.
 * @param {number} quantity - The quantity of items purchased.
 * @param {number} [discount=0] - The discount percentage to apply.
 * @returns {number} The total price after discount.
 * @throws {Error} If price or quantity is not positive.
 * @example
 * // Calculate total with 20% discount
 * const total = calculateTotal(10, 5, 20);
 * console.log(total); // 40
 */
function calculateTotal(price, quantity, discount = 0) {
    // Implementation
}

JSDoc uses /** */ comments. @param describes parameters with type and description. @returns describes the return value. @throws describes errors. @example provides usage examples. AI generates these from function signatures and implementation.

JavaScript Foundations

4.1 Client-Side Scripting

JavaScript runs in the browser, manipulating the DOM (Document Object Model), handling events, and making HTTP requests.

Key Concepts:

  • DOM Manipulation – Changing HTML elements and styles
  • Event Handling – Responding to user actions
  • HTTP Requests – Fetching data from servers
  • Browser APIs – Using built-in browser features

Code Example:

// Change HTML content
document.getElementById("demo").innerHTML = "Hello World";

// Add event listener
document.querySelector("button")?.addEventListener("click", () => {
    const element = document.getElementById("demo");
    element.innerHTML = "Button clicked!";
    element.style.color = "blue";
});

// Make HTTP request
fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
        console.log(data);
        document.getElementById("result").innerHTML = JSON.stringify(data);
    })
    .catch(error => console.error("Error:", error));

document.getElementById() finds an HTML element by ID. innerHTML changes its content. addEventListener() registers a callback for events. fetch() makes HTTP requests. then() handles the response. catch() handles errors.

4.2 Server-Side JavaScript

Node.js allows JavaScript to run on servers, handling files, databases, and HTTP requests.

Key Concepts:

  • HTTP Server – Handling client requests
  • File System – Reading and writing files
  • Database – Connecting to databases
  • APIs – Building REST endpoints

Code Example:

const http = require('http');

// Create HTTP server
const server = http.createServer((req, res) => {
    // Set response headers
    res.writeHead(200, { 'Content-Type': 'application/json' });
    
    // Response data
    const data = {
        message: "Hello Node.js!",
        timestamp: new Date().toISOString(),
        url: req.url,
        method: req.method
    };
    
    // Send response
    res.end(JSON.stringify(data));
});

// Start server on port 3000
server.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

http.createServer() creates an HTTP server. The callback receives req (request) and res (response) objects. res.writeHead() sets status and headers. res.end() sends the response. server.listen() starts the server on a port.

4.3 Event-Driven Programming

JavaScript uses events (click, load, timer) to trigger functions. The event loop manages these events.

Key Concepts:

  • Event handlers are functions designed to respond when specific events occur, such as a user clicking a button or submitting a form.
  • Event Listeners – Register handlers for specific events
  • Event Loop – Manages event processing
  • Callback Queue – Stores pending callbacks

Code Example:

// DOM events
button.addEventListener('click', () => {
    console.log('Button clicked');
});

// Timer events
setTimeout(() => {
    console.log('Timer completed');
}, 1000);

// Custom events
const customEvent = new Event('custom');
document.addEventListener('custom', () => {
    console.log('Custom event triggered');
});
document.dispatchEvent(customEvent);

// Event loop demonstration
console.log("Start");
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
console.log("End");
// Output: Start, End, Promise, Timeout

Event listeners register callbacks. The event loop manages the execution order. Microtasks (Promises) execute before macrotasks (setTimeout). This ensures predictable execution order.

4.4 Execution Context

Each function runs in an execution context that contains its variables, arguments, and this. The global execution context is established first, creating the initial environment in which the JavaScript program begins to execute.

Key Concepts:

  • Global Context – The top-level execution context
  • A function execution context is created whenever a function is invoked, providing the environment needed to execute its code.
  • Variable Environment – Stores variable bindings
  • Lexical Environment – Determines variable scope

Code Example:

// Global execution context
let globalVar = "global";

function test() {
    // Function execution context
    let localVar = "local";
    console.log(globalVar); // Access global variable
    console.log(localVar);  // Access local variable
}

test();

// Understanding context creation
function outer() {
    let x = 10;
    function inner() {
        let y = 20;
        console.log(x + y); // 30
    }
    inner();
}
outer();

Every time a function is invoked, JavaScript creates a new execution context for that call. The global execution context is established first, and each function context is then placed on the call stack while it runs. When resolving variables, JavaScript follows the scope chain to locate the appropriate binding.

4.5 Call Stack

The call stack tracks which function is currently running. Functions are added (pushed) when called and removed (popped) when they return.

Key Concepts:

  • LIFO – Last In First Out structure
  • Function Calls – Push onto the stack
  • Function Returns – Pop from the stack
  • Stack Overflow – Recursion without base case

Code Example:

function first() {
    console.log("First start");
    second();
    console.log("First end");
}

function second() {
    console.log("Second start");
    third();
    console.log("Second end");
}

function third() {
    console.log("Third");
}

first();
// Output:
// First start
// Second start
// Third
// Second end
// First end

first() is called and pushed onto the stack. It calls second(), which is pushed on top. second() calls third(), which is pushed on top. third() finishes and is popped. second() finishes and is popped. first() finishes and is popped.

4.6 Event Loop

The event loop continuously checks the call stack and the task queue.The event loop takes waiting tasks from the queue and places them on the call stack when the stack has finished its current work.

Key Concepts:

  • Call Stack – Executes synchronous code
  • Task Queue – Stores asynchronous callbacks
  • Microtask Queue – Stores Promise callbacks
  • Event Loop – Moves tasks from queue to stack

Code Example:

console.log("1");

setTimeout(() => console.log("2"), 0);

Promise.resolve().then(() => console.log("3"));

console.log("4");

// Output: 1, 4, 3, 2
  1. console.log("1") executes (sync)
  2. setTimeout is placed in the Web API
  3. Promise.resolve().then() is placed in the microtask queue
  4. console.log("4") executes (sync)
  5. Call stack is empty
  6. Microtask queue executes: console.log("3")
  7. Task queue executes: console.log("2")

4.7 Closures

A closure is a function that retains access to variables from its surrounding scope even after the outer function has finished executing.

Key Concepts:

  • An inner function can access variables from its outer function’s scope, even when those variables are not directly accessible from outside.
  • Variable Persistence – Outer variables persist in closure
  • Encapsulation – Closures provide private state
  • Factory Functions – Create functions with specific behavior

Code Example:

function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}

const counter1 = createCounter();
const counter2 = createCounter();

console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1
console.log(counter1()); // 3

// Real-world closure example
function createGreeting(greeting) {
    return function(name) {
        return `${greeting}, ${name}!`;
    };
}

const sayHello = createGreeting("Hello");
const sayHi = createGreeting("Hi");

console.log(sayHello("Alice")); // Hello, Alice!
console.log(sayHi("Bob"));      // Hi, Bob!

Each call to createCounter() creates a new closure with its own count variable. The returned function retains access to the count variable even after createCounter() has finished executing, allowing the function to preserve and use that value later. This enables private state.

4.8 Scope Chain

The scope chain determines variable accessibility based on where functions are defined (lexical scoping).

Key Concepts:

  • Lexical scoping means that a function’s variables are resolved based on where the function is defined in the source code, not where the function is called.
  • Nested scopes allow code inside an inner scope to access variables defined in its surrounding outer scopes.
  • Global Scope – Top-level scope accessible everywhere
  • Function scope means that variables declared with var are accessible throughout the function in which they are declared, rather than being limited to a block such as an if statement or loop.
  • Block Scope – Variables declared with let/const are block-scoped

Code Example:

let global = "global";

function outer() {
    let outerVar = "outer";
    
    function inner() {
        let innerVar = "inner";
        console.log(global);    // "global" (from global scope)
        console.log(outerVar);  // "outer" (from outer scope)
        console.log(innerVar);  // "inner" (from its own scope)
    }
    
    inner();
}

outer();

// Scope chain resolution
const x = 10;
function test() {
    const x = 20;
    console.log(x); // 20 (inner scope)
}
test();
console.log(x); // 10 (global scope)

The scope chain is determined by where functions are defined, not where they are called. An inner function can access variables from its surrounding outer scopes. When JavaScript looks for a variable, it first searches the current scope and then moves outward through the scope chain until the variable is found.

4.9 Hoisting

Hoisting is JavaScript’s behavior of processing variable and function declarations before executing the code, making those declarations available according to their scope.

Key Concepts:

  • Variable hoisting means that var declarations are processed before the code executes, and the variables are initialized with the value undefined until their assignment is reached.
  • Function hoisting means that function declarations are processed before execution, allowing the function to be called before its declaration appears in the source code.
  • TDZ (Temporal Dead Zone) – let and const are hoisted but not initialized
  • Function Expressions – Not hoisted like declarations

Code Example:

// Function hoisting
console.log(sum(5, 3)); // 8 (function declaration hoisted)

function sum(a, b) {
    return a + b;
}

// var hoisting
console.log(x); // undefined (hoisted but not initialized)
var x = 10;
console.log(x); // 10

// let/const TDZ
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 20;

// Function expression (not hoisted)
// greet(); // TypeError: greet is not a function
const greet = function() {
    console.log("Hello");
};
greet(); // Hello

Function declarations are fully hoisted, so they can be called before their definitions in the source code. var declarations are hoisted and initialized with undefined, while let and const declarations are hoisted but remain uninitialized in the Temporal Dead Zone (TDZ) until execution reaches their declarations. Function expressions are not hoisted as callable functions.

Core JavaScript

5.1 Variables (var, let, const, Temporal Dead Zone)

Variables store data values. JavaScript has three ways to declare variables with different behaviors.

KeywordScopeHoistingReassignRedeclare
varFunctionYes (undefined)YesYes
letBlockYes (TDZ)YesNo
constBlockYes (TDZ)NoNo

Code Example:

// var - function scoped, hoisted
var x = 10;
if (true) {
    var x = 20; // Same variable
}
console.log(x); // 20

// let - block scoped, TDZ
let y = 30;
if (true) {
    let y = 40; // Different variable
}
console.log(y); // 30

// const - block scoped, cannot reassign
const z = 50;
// z = 60; // TypeError: Assignment to constant variable

// Temporal Dead Zone
{
    // console.log(tdz); // ReferenceError: Cannot access before initialization
    let tdz = 100;
    console.log(tdz); // 100
}

// Practical example
let counter = 0;
const maxBirds = 100;

for (let i = 0; i < 5; i++) {
    counter += i;
}
// console.log(i); // ReferenceError: i is not defined (block scoped)
console.log(counter); // 10

var variables are function-scoped and can be redeclared. let variables are block-scoped, meaning they are accessible only within the block where they are declared. They also cannot be redeclared with let in the same scope.. const variables are block-scoped and cannot be reassigned. The Temporal Dead Zone (TDZ) is the period before let and const variables are initialized.

5.2 Data Types (Primitive vs Reference)

JavaScript has two categories of data types: primitive (stored directly) and reference (stored by reference).

Primitive Types:

TypeDescriptionExample
StringText data"Hello"
NumberNumeric data42, 3.14
BooleanTrue/falsetrue, false
UndefinedDeclared but not assignedundefined
NullIntentional absencenull
SymbolUnique identifierSymbol('id')
BigIntLarge integers9007199254740991n

Reference Types:

TypeDescriptionExample
ObjectKey-value pairs{name: "Alice"}
ArrayOrdered list[1, 2, 3]
FunctionCallable object() => {}

Code Example:

// PRIMITIVE TYPES
let a = 10;       // Number
let b = a;        // Copy value
b = 20;
console.log(a);   // 10 (unchanged)

let str = "Hello";
let str2 = str;
str2 = "World";
console.log(str); // "Hello" (unchanged)

// REFERENCE TYPES
let obj1 = { value: 10 };
let obj2 = obj1;  // Copy reference
obj2.value = 20;
console.log(obj1.value); // 20 (both point to same object)

let arr1 = [1, 2, 3];
let arr2 = arr1;  // Copy reference
arr2.push(4);
console.log(arr1); // [1, 2, 3, 4]

// Comparing primitive vs reference
let num1 = 5;
let num2 = 5;
console.log(num1 === num2); // true (compare values)

let objA = { x: 10 };
let objB = { x: 10 };
console.log(objA === objB); // false (different references)

Primitive types hold their values directly and are copied by value. Reference types hold a reference to an object in memory, so assigning one reference to another copies the reference rather than creating a new object. As a result, primitive comparisons compare their values, while object equality generally compares whether two references refer to the same object.

5.3 Operators

Operators perform operations on values. JavaScript provides various types of operators.

Operator Types:

TypeOperatorsExample
Arithmetic+, -, *, /, %, **5 + 3
Assignment=, +=, -=, *=, /=, %=x += 5
Comparison==, ===, !=, !==, >, <, >=, <=a > b
Logical&&, `
Ternary? :a ? b : c
Nullish??a ?? b
Optional Chaining?.user?.name

Code Example:

let a = 5;
let b = 3;
let user = null;

// Arithmetic
console.log(a + b);     // 8
console.log(a ** b);    // 125 (exponentiation)
console.log(a % b);     // 2 (modulo)

// Assignment
a += 2;  // a = a + 2
console.log(a); // 7

// Comparison
console.log(a > b);     // true
console.log(a == "7");  // true (coercion)
console.log(a === "7"); // false (strict equality)

// Logical
console.log(a > 5 && b < 5); // true
console.log(a < 5 || b > 5); // false
console.log(!(a > 5));       // false

// Ternary
let result = a > b ? "greater" : "less";
console.log(result); // "greater"

// Nullish coalescing
console.log(user?.name ?? "Guest"); // "Guest"

// Optional chaining
const person = { profile: { name: "Alice" } };
console.log(person.profile?.name); // "Alice"
console.log(person.address?.city); // undefined

Operators are evaluated according to their precedence and associativity. Precedence determines which operators are evaluated first, while associativity determines the order in which operators with the same precedence are evaluated. Comparison operators with == perform type coercion. Strict equality === checks both value and type. The nullish coalescing operator ?? only provides a default for null or undefined.

5.4 Conditionals

Conditionals control the flow of execution based on conditions.

Conditional Types:

  • if, else if, else – Execute based on boolean conditions
  • switch – Execute based on value matching

Code Example:

// if, else if, else
let score = 85;

if (score >= 90) {
    console.log("A");
} else if (score >= 75) {
    console.log("B");
} else if (score >= 60) {
    console.log("C");
} else {
    console.log("F");
}
// Output: B

// switch statement
let bird = "eagle";

switch (bird) {
    case "eagle":
        console.log("Bird of prey");
        break;
    case "sparrow":
        console.log("Songbird");
        break;
    case "hawk":
        console.log("Bird of prey");
        break;
    default:
        console.log("Unknown bird");
}
// Output: Bird of prey

// Ternary operator (shorthand)
let age = 18;
let status = age >= 18 ? "Adult" : "Minor";
console.log(status); // Adult

// Nested conditionals
let isLoggedIn = true;
let isAdmin = false;

if (isLoggedIn) {
    if (isAdmin) {
        console.log("Admin dashboard");
    } else {
        console.log("User dashboard");
    }
} else {
    console.log("Login page");
}
// Output: User dashboard

if statements execute code based on boolean conditions. switch statements compare values using strict equality. The ternary operator is a concise if-else for assignments. Nested conditionals handle multiple levels of logic.

5.5 Loops

Loops repeat code execution based on conditions.

Loop Types:

  • for – Counter-based loop
  • while – Condition-based loop
  • do...while – At least one execution
  • for...of – Iterate over iterables
  • for...in – Iterate over object properties

Code Example:

// for loop
console.log("for loop:");
for (let i = 1; i <= 5; i++) {
    console.log(i); // 1, 2, 3, 4, 5
}

// while loop
console.log("while loop:");
let i = 1;
while (i <= 5) {
    console.log(i);
    i++;
}

// do...while loop
console.log("do...while loop:");
let j = 5;
do {
    console.log(j); // 5 (executes at least once)
    j--;
} while (j > 0);

// for...of (iterables - arrays, strings, maps)
console.log("for...of loop:");
const birds = ["Eagle", "Sparrow", "Hawk"];
for (const bird of birds) {
    console.log(bird);
}

// for...in (object properties)
console.log("for...in loop:");
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
    console.log(`${key}: ${obj[key]}`);
}

// break and continue
console.log("break and continue:");
for (let i = 1; i <= 10; i++) {
    if (i === 5) break;      // stops at 5
    if (i % 2 === 0) continue; // skip even numbers
    console.log(i); // 1, 3
}

for loops are commonly used when the number of iterations is known or can be controlled by a counter or condition. while loops run until a condition is false. do...while ensures at least one execution. for...of iterates over values of iterables. for...in iterates over enumerable property keys. The break statement immediately exits the loop, while continue skips the remaining code in the current iteration and proceeds to the next iteration.

5.6 Functions

Functions are reusable blocks of code that perform specific tasks.

Function Types:

  • Function Declaration
  • Function Expression
  • Arrow Function
  • IIFE (Immediately Invoked Function Expression)
  • Higher-Order Functions

Code Example:

// Function Declaration
function add(a, b) {
    return a + b;
}
console.log(add(5, 3)); // 8

// Function Expression
const subtract = function(a, b) {
    return a - b;
};
console.log(subtract(5, 3)); // 2

// Arrow Function
const multiply = (a, b) => a * b;
console.log(multiply(5, 3)); // 15

// Arrow function with block body
const divide = (a, b) => {
    if (b === 0) return Infinity;
    return a / b;
};

// IIFE (Immediately Invoked Function Expression)
(function() {
    console.log("IIFE executed");
})();

// Higher-Order Function
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}
const double = createMultiplier(2);
console.log(double(5)); // 10

// Default Parameters
function greet(name = "Guest") {
    return `Hello, ${name}!`;
}
console.log(greet());      // Hello, Guest!
console.log(greet("Alice")); // Hello, Alice!

// Rest Parameters
function sum(...numbers) {
    return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

Function declarations are hoisted. Function expressions are not hoisted. Arrow functions have lexical this binding. IIFEs execute immediately. Higher-order functions return functions. Default parameters provide fallback values. Rest parameters collect multiple arguments into an array.

5.7 Arrays

Arrays are ordered collections used to store multiple values in a single data structure, with each element accessed by its index.

Key Methods:

MethodPurposeReturns
push()Add to endNew length
pop()Remove from endRemoved element
unshift()Add to startNew length
shift()Remove from startRemoved element
map()Transform each elementNew array
filter()Select elementsNew array
reduce()Accumulate valuesSingle value
forEach()Iterateundefined
find()Find first matchElement
some()Test if any matchBoolean
every()Test if all matchBoolean
sort()Sort elementsSorted array
slice()Extract portionNew array
splice()Insert/deleteDeleted elements
concat()Merge arraysNew array

Code Example:

const birds = ["Eagle", "Sparrow", "Hawk"];

// Adding and removing
birds.push("Robin");        // Add to end
birds.unshift("Cardinal");  // Add to start
const last = birds.pop();   // Remove from end
const first = birds.shift(); // Remove from start
console.log(birds); // ["Sparrow", "Hawk", "Robin"]

// Iteration
birds.forEach(bird => console.log(bird));

// Transformation
const uppercase = birds.map(bird => bird.toUpperCase());
console.log(uppercase); // ["SPARROW", "HAWK", "ROBIN"]

// Filtering
const longBirds = birds.filter(bird => bird.length > 5);
console.log(longBirds); // ["Sparrow", "Robin"]

// Reducing
const lengths = birds.map(bird => bird.length);
const totalLength = lengths.reduce((acc, len) => acc + len, 0);
console.log(totalLength); // 15

// Finding
const found = birds.find(bird => bird.startsWith("H"));
console.log(found); // "Hawk"

// Checking
const hasEagle = birds.some(bird => bird === "Eagle");
const allLong = birds.every(bird => bird.length > 3);
console.log(hasEagle); // false
console.log(allLong);  // true

// Slicing and splicing
const sliced = birds.slice(1, 3);
console.log(sliced); // ["Hawk", "Robin"]
birds.splice(1, 1, "Eagle");
console.log(birds); // ["Sparrow", "Eagle", "Robin"]

Arrays are zero-indexed. Mutating methods modify the original array. Non-mutating methods return new arrays. filter(), map(), and reduce() are functional methods that don’t modify the original array.

5.8 Objects

Objects are collections of key-value pairs that store data and behavior.

Object Creation Methods:

  • Object literal {}
  • new Object() constructor
  • Object.create()
  • Class constructor

Code Example:

// Object literal
const user = {
    name: "Alice",
    age: 25,
    greet() {
        console.log(`Hi, I'm ${this.name}`);
    }
};
user.greet(); // Hi, I'm Alice

// Computed property names
const key = "age";
console.log(user[key]); // 25

// Object methods
const keys = Object.keys(user);
const values = Object.values(user);
const entries = Object.entries(user);
console.log(keys);   // ["name", "age", "greet"]
console.log(values); // ["Alice", 25, function]
console.log(entries); // [["name","Alice"], ["age",25], ["greet", function]]

// Object spread
const userCopy = { ...user, location: "NYC" };
console.log(userCopy); // { name: "Alice", age: 25, location: "NYC" }

// Destructuring
const { name, age } = user;
console.log(name, age); // Alice 25

// Dynamic property access
const property = "name";
console.log(user[property]); // Alice

// Object assign
const target = { a: 1 };
const source = { b: 2, c: 3 };
Object.assign(target, source);
console.log(target); // { a: 1, b: 2, c: 3 }

// Object freeze (immutable)
const immutable = Object.freeze({ value: 10 });
// immutable.value = 20; // Error in strict mode

// Object seal (can't add/delete, can modify existing)
const sealed = Object.seal({ value: 10 });
sealed.value = 20; // OK
// delete sealed.value; // Error

Objects are reference types. Properties can be accessed using dot notation or bracket notation. Object methods use this to refer to the current object. Object spread and Object.assign() create shallow copies. Object.freeze() makes objects immutable.

5.9 Type Conversion & Coercion, Strict Mode, Equality

Type conversion changes a value from one type to another.

Explicit Conversion:

  • String() – Convert to string
  • Number() – Convert to number
  • Boolean() – Convert to boolean
  • parseInt() – Parse integer from string
  • parseFloat() – Parse float from string

Implicit Coercion:

  • Automatic conversion in operations
  • == performs type coercion
  • + with strings concatenates

Strict Mode:

  • "use strict" at the top of a file or function
  • Catches common coding mistakes
  • Prevents accidental globals

Code Example:

// Explicit conversion
console.log(String(42));       // "42"
console.log(Number("3.14"));   // 3.14
console.log(Boolean(0));       // false
console.log(parseInt("42px")); // 42
console.log(parseFloat("3.14")); // 3.14

// Implicit coercion
console.log("5" + 3);  // "53" (string concatenation)
console.log("5" - 3);  // 2 (numeric subtraction)
console.log("5" * 3);  // 15
console.log("5" / 3);  // 1.666...
console.log("5" == 5); // true (coercion)
console.log("5" === 5); // false (strict)

// Truthy and Falsy values
// Falsy: false, 0, "", null, undefined, NaN
// Truthy: everything else
if ("Hello") console.log("Truthy");
if (!0) console.log("Falsy");

// Strict mode
"use strict";
let x = 10;
// y = 20; // Error: y is not defined (strict mode catches this)

// Equality comparison
console.log(null == undefined);  // true
console.log(null === undefined); // false
console.log(0 == false);         // true
console.log(0 === false);        // false
console.log(1 == true);          // true
console.log(1 === true);         // false

// Avoiding coercion pitfalls
function sumSafe(a, b) {
    return Number(a) + Number(b);
}
console.log(sumSafe("5", "3")); // 8

Explicit conversion uses built-in functions. Implicit coercion happens automatically. == compares after coercion, === compares without coercion. Strict mode prevents unsafe operations. Falsy values are values that JavaScript treats as false when used in a Boolean context. Common falsy values include false, 0, "", null, undefined, and NaN.

5.10 Error Handling

Error handling gracefully manages runtime errors.

Error Types:

  • SyntaxError – Invalid code syntax
  • ReferenceError – Undefined variable
  • TypeError – Invalid type operation
  • RangeError – Value out of range
  • Error – Generic error

Code Example:

// try...catch...finally
try {
    let result = riskyOperation();
    console.log(result);
} catch (error) {
    console.log("Error occurred:", error.message);
} finally {
    console.log("This always runs");
}

// Custom errors
class BirdError extends Error {
    constructor(message) {
        super(message);
        this.name = "BirdError";
    }
}

// Throwing errors
function validateBird(species, count) {
    if (!species || species.length === 0) {
        throw new BirdError("Species cannot be empty");
    }
    if (count < 0) {
        throw new Error("Count cannot be negative");
    }
    return { species, count };
}

// Handling errors
try {
    validateBird("", 10);
} catch (error) {
    if (error instanceof BirdError) {
        console.log("Bird error:", error.message);
    } else {
        console.log("General error:", error.message);
    }
}

// Async error handling
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
        }
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Fetch error:", error.message);
        throw error; // Re-throw
    }
}

// Custom error with status
class ApiError extends Error {
    constructor(message, status) {
        super(message);
        this.name = "ApiError";
        this.status = status;
    }
}

try contains code that might throw an error. catch handles thrown errors. finally always executes. Custom errors extend Error. throw creates and throws an error. Async errors are caught with try...catch around await.

5.11 Regular Expressions

Regular expressions provide pattern matching for strings.

Common Patterns:

PatternDescriptionExample
\dDigit/\d/ matches “5”
\wWord character/\w/ matches “a”
\sWhitespace/\s/ matches ” “
.Any character/./ matches any
*0 or more/a*/ matches “aaa”
+1 or more/a+/ matches “a”
?0 or 1/a?/ matches “”
{n}Exactly n/\d{3}/ matches “123”
^Start of string/^Hello/
$End of string/World$/

Code Example:

// Creating regex
const regex1 = new RegExp("\\d+");
const regex2 = /\d+/g;

// String methods with regex
const str = "Bird count: 10 eagles, 5 sparrows, 3 hawks";

// test() - check if match exists
console.log(/\d+/.test(str)); // true

// match() - find matches
const matches = str.match(/\d+/g);
console.log(matches); // ["10", "5", "3"]

// replace() - replace matches
const replaced = str.replace(/\d+/g, "X");
console.log(replaced); // "Bird count: X eagles, X sparrows, X hawks"

// exec() - execute search
const regex = /\d+/g;
let result;
while ((result = regex.exec(str)) !== null) {
    console.log(`Found ${result[0]} at index ${result.index}`);
}
// Found 10 at index 11
// Found 5 at index 21
// Found 3 at index 33

// Split string
const parts = str.split(/\s+/);
console.log(parts); // ["Bird", "count:", "10", "eagles,", "5", "sparrows,", "3", "hawks"]

// Validate email
function isValidEmail(email) {
    const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    return emailRegex.test(email);
}
console.log(isValidEmail("test@example.com")); // true
console.log(isValidEmail("invalid-email")); // false

// Extract information
function extractBirds(text) {
    const pattern = /(\d+)\s+(\w+)/g;
    const birds = [];
    let match;
    while ((match = pattern.exec(text)) !== null) {
        birds.push({ count: parseInt(match[1]), species: match[2] });
    }
    return birds;
}
console.log(extractBirds("10 eagles, 5 sparrows, 3 hawks"));
// [{count:10, species:"eagles"}, {count:5, species:"sparrows"}, {count:3, species:"hawks"}]

Regular expressions define search patterns. test() returns boolean. match() returns matches. replace() substitutes matches. exec() finds matches with position. Capture groups () extract sub-patterns. Flags like g (global) and i (case-insensitive) modify behavior.

5.12 Memory (Stack vs Heap, Garbage Collection)

JavaScript manages memory with a stack and heap. Garbage collection automatically frees unused memory.

Stack Memory:

  • Stores primitive values and function calls
  • Fast access, fixed size
  • LIFO (Last In First Out)
  • Automatic cleanup when function returns

Heap Memory:

  • Stores objects, arrays, and functions
  • Larger size, dynamic allocation
  • Access via references
  • Garbage collected

Garbage Collection:

  • Automatically frees memory
  • Mark-and-sweep algorithm
  • Removes unreachable objects
  • Memory leaks occur when references persist

Code Example:

// Stack memory example
function stackExample() {
    let a = 10;        // Stored on stack
    let b = 20;        // Stored on stack
    let c = a + b;     // Calculated on stack
    return c;
}
stackExample(); // Memory freed after function returns

// Heap memory example
let obj1 = { value: 10 };     // Object on heap, reference on stack
let obj2 = { value: 20 };     // Another object on heap
let obj3 = obj1;              // obj3 references same object as obj1

// Modifying object on heap
obj1.value = 30;
console.log(obj3.value); // 30 (same object)

// Garbage collection demonstration
function createLargeObject() {
    let huge = new Array(10000); // Allocated on heap
    return huge;
}

let data = createLargeObject(); // Referenced
// Later:
data = null; // Now eligible for garbage collection

// Memory leak example
let cache = {};
function createLeak() {
    let localData = new Array(10000);
    cache['key'] = localData; // Stored in global cache
    // Even when function returns, localData persists in cache
}

// Circular reference (older JavaScript)
function createCircular() {
    let objA = {};
    let objB = {};
    objA.ref = objB;
    objB.ref = objA;
    return objA;
}
// Modern garbage collectors handle circular references

// WeakMap - prevents memory leaks
let weakCache = new WeakMap();
function storeData(obj, data) {
    weakCache.set(obj, data);
}
let user = { name: "Alice" };
storeData(user, { extra: "data" });
// When user becomes unreachable, data can be garbage collected

Stack memory handles function calls and primitive values. Heap memory stores objects. Garbage collection automatically frees unreachable objects. References prevent garbage collection. Use WeakMap and WeakSet for cache that doesn’t prevent garbage collection.

Object-Oriented Programming

6.1 Encapsulation

Encapsulation bundles data and methods into an object, hiding internal details.

  • Data hiding – Keep internal state private
  • Public interface – Expose only necessary methods
  • Private fields – Use # for truly private fields
  • Getters/Setters – Controlled access to properties

Code Example:

class BankAccount {
    #balance = 0;  // Private field
    
    constructor(owner) {
        this.owner = owner;
        this.#balance = 0;
    }
    
    // Public method to deposit
    deposit(amount) {
        if (amount <= 0) {
            throw new Error("Deposit must be positive");
        }
        this.#balance += amount;
        this.#logTransaction("deposit", amount);
        return this.#balance;
    }
    
    // Public method to withdraw
    withdraw(amount) {
        if (amount <= 0) {
            throw new Error("Withdrawal must be positive");
        }
        if (amount > this.#balance) {
            throw new Error("Insufficient funds");
        }
        this.#balance -= amount;
        this.#logTransaction("withdraw", amount);
        return this.#balance;
    }
    
    // Public getter
    get balance() {
        return this.#balance;
    }
    
    // Private method
    #logTransaction(type, amount) {
        console.log(`${type}: ${amount}, new balance: ${this.#balance}`);
    }
}

// Usage
const account = new BankAccount("Alice");
account.deposit(100);
account.withdraw(30);
console.log(account.balance); // 70
// console.log(account.#balance); // Error: Private field
// account.#logTransaction(); // Error: Private method

The # prefix creates private fields and methods. Private members are accessible only from within the class where they are declared and cannot be accessed directly from outside the class.Public methods provide controlled access. Getters provide read-only access. This protects internal state from external modification.

6.2 Abstraction

Abstraction hides implementation complexity, exposing only necessary methods.

  • Simplified interface – Hide complex details
  • Implementation hiding – Internal logic is hidden
  • Focus on what – Expose what it does, not how
  • Reduce complexity – Simpler for users

Code Example:

class CoffeeMachine {
    #waterAmount = 0;
    #coffeeBeans = 0;
    #temperature = 20;
    
    constructor(beans) {
        this.#coffeeBeans = beans;
    }
    
    // Simple public interface
    makeCoffee(cups = 1) {
        this.#heatWater();
        this.#grindBeans(cups);
        this.#brew(cups);
        return "Coffee is ready! ☕";
    }
    
    // Internal implementation details (abstracted)
    #heatWater() {
        this.#temperature = 95;
        console.log("Heating water...");
    }
    
    #grindBeans(cups) {
        const needed = cups * 10;
        if (this.#coffeeBeans < needed) {
            throw new Error("Not enough coffee beans");
        }
        this.#coffeeBeans -= needed;
        console.log(`Grinding ${needed}g of beans...`);
    }
    
    #brew(cups) {
        console.log(`Brewing ${cups} cups...`);
    }
    
    // Public interface for settings
    addWater(ml) {
        this.#waterAmount += ml;
        console.log(`${ml}ml water added`);
    }
    
    addBeans(g) {
        this.#coffeeBeans += g;
        console.log(`${g}g beans added`);
    }
}

const machine = new CoffeeMachine(100);
machine.addWater(500);
console.log(machine.makeCoffee(2));
// Simple interface hides complex process

The user only interacts with makeCoffee(), addWater(), and addBeans(). The complex internal process (heating, grinding, brewing) is hidden. This reduces complexity and makes the class easier to use.

6.3 Inheritance

A class can extend another class using extends. The child inherits properties and methods.

  • Parent class – Base class providing functionality
  • Child class – Inherits from parent
  • super() – Call parent constructor
  • Method overriding allows a child class to provide its own implementation of a method inherited from its parent class.
  • super.method() – Call parent methods

Code Example:

// Parent class
class Animal {
    constructor(name) {
        this.name = name;
        this.legs = 4;
    }
    
    speak() {
        console.log(`${this.name} makes a sound`);
    }
    
    move() {
        console.log(`${this.name} moves`);
    }
}

// Child class inheriting from Animal
class Bird extends Animal {
    constructor(name, color) {
        super(name);  // Call parent constructor
        this.color = color;
        this.legs = 2;  // Override parent property
    }
    
    // Override parent method
    speak() {
        console.log(`${this.name} chirps`);
    }
    
    // New method
    fly() {
        console.log(`${this.name} flies with ${this.color} wings`);
    }
}

// Another child class
class Eagle extends Bird {
    constructor(name) {
        super(name, "brown");
        this.canHunt = true;
    }
    
    // Override parent method
    speak() {
        super.speak(); // Call parent method
        console.log(`${this.name} screeches loudly!`);
    }
    
    // New method
    hunt() {
        if (this.canHunt) {
            console.log(`${this.name} is hunting`);
        }
    }
}

// Usage
const sparrow = new Bird("Sparrow", "gray");
sparrow.speak(); // Sparrow chirps
sparrow.move();  // Sparrow moves
sparrow.fly();   // Sparrow flies with gray wings

const eagle = new Eagle("Golden Eagle");
eagle.speak(); // Golden Eagle chirps, Golden Eagle screeches loudly!
eagle.hunt();  // Golden Eagle is hunting

extends establishes the inheritance chain. super() calls the parent constructor. Child classes can override methods. super.method() calls parent methods. Inheritance enables code reuse and polymorphism.

6.4 Polymorphism

Same method name, different implementations based on object type.

  • Method overriding – Different implementations
  • Duck typing – “If it walks like a duck…”
  • Interface – Same method signature
  • Runtime polymorphism – Behavior determined at runtime

Code Example:

// Base classes
class Bird {
    constructor(name) {
        this.name = name;
    }
    
    speak() {
        console.log(`${this.name} makes a bird sound`);
    }
}

class Sparrow extends Bird {
    speak() {
        console.log(`${this.name} chirps: "Chirp!"`);
    }
}

class Eagle extends Bird {
    speak() {
        console.log(`${this.name} screeches: "SCREECH!"`);
    }
}

class Penguin extends Bird {
    speak() {
        console.log(`${this.name} honks: "HONK!"`);
    }
}

// Polymorphic function
function birdConcert(birds) {
    console.log("--- Bird Concert ---");
    for (const bird of birds) {
        bird.speak();  // Different sounds based on bird type
    }
}

// Usage
const birds = [
    new Sparrow("House Sparrow"),
    new Eagle("Golden Eagle"),
    new Penguin("Emperor Penguin"),
    new Sparrow("Tree Sparrow")
];

birdConcert(birds);
// Output:
// House Sparrow chirps: "Chirp!"
// Golden Eagle screeches: "SCREECH!"
// Emperor Penguin honks: "HONK!"
// Tree Sparrow chirps: "Chirp!"

// Duck typing example
function makeSound(thing) {
    if (typeof thing.speak === 'function') {
        thing.speak();
    } else {
        console.log(`${thing.name} cannot speak`);
    }
}

// Object with speak method (duck typing)
const robotBird = {
    name: "Robot",
    speak() {
        console.log(`${this.name} beeps: "BEEP!"`);
    }
};

makeSound(robotBird); // Robot beeps: "BEEP!"

Polymorphism allows objects of different classes to respond to the same method call according to their own implementations of that method. The method called depends on the object’s actual type. Duck typing checks for method existence rather than type. This makes code more flexible and extensible.

6.5 Constructor Functions (pre-ES6)

Functions used as constructors with new. Prototype methods are shared.

  • Constructor function – Function used with new
  • this binding – this refers to new object
  • Prototype methods – Shared across instances
  • Instance properties – Unique to each instance

Code Example:

// Constructor function
function Bird(species, color) {
    // Instance properties
    this.species = species;
    this.color = color;
    this.legs = 2;
    this.eggs = 0;
}

// Prototype methods (shared across all instances)
Bird.prototype.speak = function() {
    console.log(`${this.species} chirps`);
};

Bird.prototype.fly = function() {
    console.log(`${this.species} flies with ${this.color} wings`);
};

Bird.prototype.layEgg = function() {
    this.eggs++;
    console.log(`${this.species} laid an egg. Total: ${this.eggs}`);
};

// Static property
Bird.maxWingspan = 3.7;

// Static method
Bird.isBird = function(obj) {
    return obj instanceof Bird;
};

// Usage
const sparrow = new Bird("Sparrow", "brown");
const eagle = new Bird("Eagle", "golden");

sparrow.speak(); // Sparrow chirps
eagle.speak();   // Eagle chirps
sparrow.layEgg(); // Sparrow laid an egg. Total: 1
sparrow.layEgg(); // Sparrow laid an egg. Total: 2
eagle.layEgg();   // Eagle laid an egg. Total: 1

// Checking instance
console.log(Bird.isBird(sparrow)); // true
console.log(Bird.isBird({}));      // false

// Prototype chain
console.log(sparrow.__proto__ === Bird.prototype); // true
console.log(Bird.prototype.__proto__ === Object.prototype); // true

The new operator creates a new object, sets its prototype, and binds this to it. Prototype methods are shared, saving memory. Instance properties are unique. Static properties and methods belong to the class or constructor function itself rather than to individual instances created from it.

6.6 Prototypes

Every JavaScript object has a prototype (except null). Prototypes allow inheritance of properties.

  • Prototype – Object that serves as a template
  • Prototype chain – Links objects together
  • __proto__ – Internal prototype reference (deprecated)
  • Object.getPrototypeOf() – Get prototype
  • Object.setPrototypeOf() – Set prototype

Code Example:

// Creating a prototype
const birdPrototype = {
    speak() {
        console.log(`${this.species} chirps`);
    },
    fly() {
        console.log(`${this.species} flies`);
    }
};

// Creating objects with the prototype
const sparrow = Object.create(birdPrototype);
sparrow.species = "Sparrow";
sparrow.color = "brown";

const eagle = Object.create(birdPrototype);
eagle.species = "Eagle";
eagle.color = "golden";

sparrow.speak(); // Sparrow chirps
eagle.speak();   // Eagle chirps

// Adding methods to prototype
birdPrototype.layEgg = function() {
    console.log(`${this.species} laid an egg`);
};

sparrow.layEgg(); // Sparrow laid an egg
eagle.layEgg();   // Eagle laid an egg

// Prototype chain
function Animal(name) {
    this.name = name;
}
Animal.prototype.walk = function() {
    console.log(`${this.name} walks`);
};

function Bird(name) {
    Animal.call(this, name);
    this.wings = 2;
}

// Set up inheritance
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;

Bird.prototype.fly = function() {
    console.log(`${this.name} flies with ${this.wings} wings`);
};

const robin = new Bird("Robin");
robin.walk(); // Robin walks
robin.fly();  // Robin flies with 2 wings

// Prototype inspection
console.log(Object.getPrototypeOf(robin) === Bird.prototype); // true
console.log(Object.getPrototypeOf(Bird.prototype) === Animal.prototype); // true

Prototypes are objects that other objects inherit from. Object.create() creates an object with a specific prototype. The prototype chain is followed when accessing properties. Methods added to a prototype are available to all objects inheriting from it.

6.7 Prototype Chain

When accessing a property, JavaScript looks at the object itself, then its prototype, then the prototype’s prototype, until null.

  • Property lookup – Search object, then prototype chain
  • Prototype link – __proto__ links to prototype
  • Inheritance – Properties inherited through chain
  • Shadowing – Object properties override prototype properties
  • hasOwnProperty() – Check own properties

Code Example:

// Creating a chain
const grandParent = {
    species: "Unknown",
    legs: 4
};

const parent = Object.create(grandParent);
parent.name = "Parent";
parent.color = "brown";

const child = Object.create(parent);
child.name = "Child";
child.age = 2;

// Property access goes up the chain
console.log(child.name);     // "Child" (own property)
console.log(child.color);    // "brown" (from parent)
console.log(child.legs);     // 4 (from grandParent)
console.log(child.species);  // "Unknown" (from grandParent)

// Shadowing
child.legs = 2;
console.log(child.legs);     // 2 (shadowing grandParent's legs)

// Checking own properties
console.log(child.hasOwnProperty('name'));   // true
console.log(child.hasOwnProperty('color'));  // false
console.log(child.hasOwnProperty('legs'));   // true (shadowed)

// Prototype chain inspection
console.log(Object.getPrototypeOf(child) === parent);        // true
console.log(Object.getPrototypeOf(parent) === grandParent);  // true
console.log(Object.getPrototypeOf(grandParent) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null (end of chain)

// Iterating over properties
console.log("=== Own properties ===");
for (const key in child) {
    if (child.hasOwnProperty(key)) {
        console.log(`${key}: ${child[key]}`);
    }
}
// name: Child
// age: 2
// legs: 2

console.log("=== All properties ===");
for (const key in child) {
    console.log(`${key}: ${child[key]}`);
}
// name: Child, age: 2, legs: 2, color: brown, species: Unknown

Property lookup searches an object’s prototype chain until the requested property is found or the chain ends. A typical chain is child → parent → grandParent → Object.prototype → null. The hasOwnProperty() method checks whether a property belongs directly to the object rather than being inherited. Shadowing occurs when an object defines its own property with the same name as a property inherited from its prototype.

6.8 ES6 Classes

Syntactic sugar over prototypes. class, constructor, extends, super.

  • class – Defines a class
  • constructor – Initializes instances
  • extends – Inherits from parent class
  • super – Calls parent constructor or methods
  • Methods – Defined without function keyword

Code Example:

// ES6 Class syntax
class Bird {
    // Class properties (public fields)
    species = "Unknown";
    legs = 2;
    #eggs = 0;  // Private field
    
    // Constructor
    constructor(species, color) {
        this.species = species;
        this.color = color;
    }
    
    // Methods
    speak() {
        console.log(`${this.species} chirps`);
    }
    
    fly() {
        console.log(`${this.species} flies with ${this.color} wings`);
    }
    
    layEgg() {
        this.#eggs++;
        console.log(`${this.species} laid an egg. Total: ${this.#eggs}`);
    }
    
    // Getter
    get eggCount() {
        return this.#eggs;
    }
    
    // Setter
    set speciesName(value) {
        if (!value || value.length === 0) {
            throw new Error("Species cannot be empty");
        }
        this.species = value;
    }
    
    // Static method
    static isBird(obj) {
        return obj instanceof Bird;
    }
    
    // Static property
    static maxWingspan = 3.7;
}

// Subclass
class Eagle extends Bird {
    constructor(species, color, canHunt = true) {
        super(species, color);  // Call parent constructor
        this.canHunt = canHunt;
        this.legs = 2;
    }
    
    // Override method
    speak() {
        super.speak();  // Call parent method
        console.log(`${this.species} screeches loudly!`);
    }
    
    // New method
    hunt() {
        if (this.canHunt) {
            console.log(`${this.species} is hunting`);
        }
    }
}

// Usage
const sparrow = new Bird("Sparrow", "brown");
sparrow.speak();      // Sparrow chirps
sparrow.layEgg();     // Sparrow laid an egg. Total: 1
console.log(sparrow.eggCount); // 1

const eagle = new Eagle("Golden Eagle", "golden");
eagle.speak();   // Golden Eagle chirps, Golden Eagle screeches loudly!
eagle.hunt();    // Golden Eagle is hunting

// Static members
console.log(Bird.maxWingspan); // 3.7
console.log(Bird.isBird(sparrow)); // true

Classes provide a cleaner and more structured syntax for creating objects and working with JavaScript’s underlying prototype-based inheritance. constructor initializes instances. extends sets up inheritance. super calls parent methods. Private fields start with #. Static members belong to the class itself.

6.9 Static Methods & Properties

Static members belong to the class itself, not instances.

  • static – Defines static member
  • Class-level – Belongs to class, not instances
  • Utility functions – Helper methods
  • Shared data – Data shared across instances
  • Factory methods – Create instances

Code Example:

class BirdTracker {
    // Static properties
    static totalBirds = 0;
    static allSpecies = [];
    static maxBirds = 100;
    
    // Static method - utility
    static addBird(bird) {
        if (BirdTracker.totalBirds >= BirdTracker.maxBirds) {
            console.log("Cannot add more birds - limit reached");
            return false;
        }
        BirdTracker.totalBirds++;
        BirdTracker.allSpecies.push(bird.species);
        return true;
    }
    
    static getTotalBirds() {
        return BirdTracker.totalBirds;
    }
    
    static getUniqueSpecies() {
        return [...new Set(BirdTracker.allSpecies)];
    }
    
    static reset() {
        BirdTracker.totalBirds = 0;
        BirdTracker.allSpecies = [];
        console.log("Tracker reset");
    }
    
    // Static factory method
    static createBird(species, color) {
        const bird = new Bird(species, color);
        BirdTracker.addBird(bird);
        return bird;
    }
    
    // Static property with getter
    static get isFull() {
        return BirdTracker.totalBirds >= BirdTracker.maxBirds;
    }
}

class Bird {
    constructor(species, color) {
        this.species = species;
        this.color = color;
    }
    
    speak() {
        console.log(`${this.species} chirps`);
    }
}

// Usage - no instance needed
console.log(BirdTracker.totalBirds); // 0
console.log(BirdTracker.isFull);     // false

const sparrow = BirdTracker.createBird("Sparrow", "brown");
const eagle = BirdTracker.createBird("Eagle", "golden");
const hawk = BirdTracker.createBird("Hawk", "gray");

console.log(BirdTracker.getTotalBirds()); // 3
console.log(BirdTracker.getUniqueSpecies()); // ["Sparrow", "Eagle", "Hawk"]

// Using static method
BirdTracker.reset();
console.log(BirdTracker.getTotalBirds()); // 0

Static members are accessed through the class name, not instances. They’re useful for utility functions and shared data. Factory methods create and track instances. Static getters provide computed values.

6.10 Private Fields (#)

Truly private instance fields using #. Only accessible inside the class.

  • #field – Private field syntax
  • Encapsulation – Complete data hiding
  • Access control – Only within class
  • No external access – Cannot be accessed outside
  • No inheritance – Not inherited by subclasses

Code Example:

class BirdAccount {
    // Private fields
    #id = 0;
    #balance = 0;
    #transactions = [];
    #isActive = true;
    
    // Private static field
    static #totalAccounts = 0;
    
    constructor(species, initialBalance = 0) {
        this.species = species;
        this.#id = ++BirdAccount.#totalAccounts;
        this.#balance = initialBalance;
        this.#logTransaction("initial", initialBalance);
        console.log(`Account created for ${species} (ID: ${this.#id})`);
    }
    
    // Public methods
    deposit(amount) {
        if (amount <= 0) {
            throw new Error("Amount must be positive");
        }
        this.#balance += amount;
        this.#logTransaction("deposit", amount);
        return this.#balance;
    }
    
    withdraw(amount) {
        if (amount <= 0) {
            throw new Error("Amount must be positive");
        }
        if (amount > this.#balance) {
            throw new Error("Insufficient funds");
        }
        this.#balance -= amount;
        this.#logTransaction("withdraw", amount);
        return this.#balance;
    }
    
    // Getters
    get balance() {
        return this.#balance;
    }
    
    get id() {
        return this.#id;
    }
    
    get transactionCount() {
        return this.#transactions.length;
    }
    
    // Get transaction history (read-only copy)
    getTransactionHistory() {
        return [...this.#transactions];
    }
    
    // Private method
    #logTransaction(type, amount) {
        this.#transactions.push({
            type,
            amount,
            timestamp: new Date().toISOString(),
            balance: this.#balance
        });
    }
    
    // Static method
    static getTotalAccounts() {
        return BirdAccount.#totalAccounts;
    }
}

// Usage
const sparrowAccount = new BirdAccount("Sparrow", 100);
const eagleAccount = new BirdAccount("Eagle", 50);

sparrowAccount.deposit(25);
sparrowAccount.withdraw(10);

console.log(sparrowAccount.balance); // 115
console.log(sparrowAccount.id);      // 1
console.log(sparrowAccount.transactionCount); // 3

// Private fields are not accessible
// console.log(sparrowAccount.#balance); // SyntaxError
// console.log(sparrowAccount.#transactions); // SyntaxError
// console.log(sparrowAccount.#logTransaction); // SyntaxError

// Get transaction history (safe copy)
const history = sparrowAccount.getTransactionHistory();
console.log(history);
// [
//   { type: 'initial', amount: 100, timestamp: '...', balance: 100 },
//   { type: 'deposit', amount: 25, timestamp: '...', balance: 125 },
//   { type: 'withdraw', amount: 10, timestamp: '...', balance: 115 }
// ]

console.log(BirdAccount.getTotalAccounts()); // 2

Private fields use # prefix and are only accessible inside the class. They provide true encapsulation. Subclasses cannot access private fields from their parent. Getters provide controlled read access. Private methods are also hidden.

6.11 Object.create()

Creates a new object with the specified prototype.

  • Prototype specification – Choose the prototype
  • Property descriptors – Define property attributes
  • Object inheritance – Inherit from any object
  • Classless inheritance – Without using classes
  • Custom objects – Build custom prototypes

Code Example:

// Basic Object.create
const birdPrototype = {
    speak() {
        console.log(`${this.species} chirps`);
    },
    fly() {
        console.log(`${this.species} flies`);
    },
    layEgg() {
        this.eggs = (this.eggs || 0) + 1;
        console.log(`${this.species} laid an egg. Total: ${this.eggs}`);
    }
};

const sparrow = Object.create(birdPrototype);
sparrow.species = "Sparrow";
sparrow.color = "brown";

const eagle = Object.create(birdPrototype);
eagle.species = "Eagle";
eagle.color = "golden";

sparrow.speak(); // Sparrow chirps
eagle.fly();     // Eagle flies

// Property descriptors
const birdWithDescriptors = Object.create(null, {
    species: {
        value: "Hawk",
        writable: true,
        enumerable: true,
        configurable: false
    },
    color: {
        value: "gray",
        writable: false,  // Read-only
        enumerable: true,
        configurable: false
    }
});

console.log(birdWithDescriptors.species); // Hawk
birdWithDescriptors.species = "Falcon";
console.log(birdWithDescriptors.species); // Falcon
// birdWithDescriptors.color = "black"; // Error in strict mode (writable: false)

// Factory function using Object.create
function createBird(species, color) {
    const bird = Object.create(birdPrototype);
    bird.species = species;
    bird.color = color;
    bird.eggs = 0;
    return bird;
}

const robin = createBird("Robin", "red");
const cardinal = createBird("Cardinal", "red");
robin.speak(); // Robin chirps
cardinal.speak(); // Cardinal chirps

// Multiple inheritance (mixin) using Object.create
const swimMixin = {
    swim() {
        console.log(`${this.species} swims`);
    }
};

const flyAndSwimBird = Object.create(
    Object.assign({}, birdPrototype, swimMixin)
);

const duck = Object.create(flyAndSwimBird);
duck.species = "Duck";
duck.color = "white";
duck.fly();  // Duck flies
duck.swim(); // Duck swims

Object.create() creates a new object with the specified prototype. It allows inheritance without classes. Property descriptors control property behavior. Multiple mixins can be combined. This provides flexibility in object creation.

6.12 this Keyword

Refers to the execution context. Inside a method, this generally refers to the object that the method is called on. In a regular function (non-strict), this is global. Arrow functions inherit this from outer scope.

  • In a method call, this generally refers to the object on which the method is invoked.
  • Function call – this is global (window) in non-strict, undefined in strict
  • Arrow function – Lexical this from outer scope
  • Constructor – this refers to new instance
  • Explicit binding – call, apply, bind
  • Event listeners – this refers to the element

Code Example:

// Method call
const bird = {
    name: "Sparrow",
    greet() {
        console.log(`Hello, I'm ${this.name}`);
    }
};
bird.greet(); // Hello, I'm Sparrow

// Function call
function showThis() {
    console.log(this);
}
// In browser: window
// In strict mode: undefined

// Arrow functions (lexical this)
const arrowBird = {
    name: "Eagle",
    greet: () => {
        console.log(`Hello, I'm ${this.name}`); // this is lexical (outer scope)
    }
};
arrowBird.greet(); // Hello, I'm undefined (this not bound to arrowBird)

// Constructor function
function Bird(species) {
    this.species = species;
    this.getName = function() {
        return this.species;
    };
}
const sparrow = new Bird("Sparrow");
console.log(sparrow.getName()); // Sparrow

// Explicit binding with call
function greet() {
    console.log(`Hello, ${this.name}`);
}
const bird2 = { name: "Hawk" };
greet.call(bird2); // Hello, Hawk

// apply (call with arguments)
function introduce(greeting, punctuation) {
    console.log(`${greeting}, ${this.name}${punctuation}`);
}
introduce.apply(bird2, ["Hi", "!"]); // Hi, Hawk!

// bind (create new function with bound this)
const boundGreet = greet.bind(bird2);
boundGreet(); // Hello, Hawk

// Event listener example (browser)
// button.addEventListener('click', function() {
//     console.log(this); // this refers to button element
// });

// Method chaining using this
class BirdCounter {
    constructor() {
        this.count = 0;
    }
    
    add() {
        this.count++;
        return this;  // Enable chaining
    }
    
    subtract() {
        this.count--;
        return this;
    }
    
    show() {
        console.log(this.count);
        return this;
    }
}

const counter = new BirdCounter();
counter.add().add().show().subtract().show(); // 2, 1

The value of this in JavaScript is determined by how a function is invoked, rather than simply where the function is defined. Methods get the object as this. Regular functions get the global object or undefined in strict mode. Arrow functions get this from the enclosing scope.The call(), apply(), and bind() methods allow you to explicitly specify the value of this when working with a function.

Advanced JavaScript

7.1 Promises

A Promise represents a future value. It has three states: pending, fulfilled, rejected.

  • Pending – Initial state, not yet resolved
  • Fulfilled – Operation completed successfully
  • Rejected – Operation failed
  • Chaining – .then(), .catch(), .finally()
  • Static methods – Promise.all(), Promise.race(), Promise.resolve()

Code Example:

// Creating a Promise
const fetchData = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve({ data: "Bird data loaded", status: 200 });
        } else {
            reject(new Error("Failed to load bird data"));
        }
    }, 1000);
});

// Using the Promise
fetchData
    .then(result => {
        console.log("Success:", result.data);
        return result.data;
    })
    .then(data => {
        console.log("Processing:", data);
        return data.toUpperCase();
    })
    .then(processed => {
        console.log("Processed:", processed);
    })
    .catch(error => {
        console.error("Error:", error.message);
    })
    .finally(() => {
        console.log("Cleanup completed");
    });

// Promise.all - wait for all
const promises = [
    Promise.resolve(10),
    Promise.resolve(20),
    Promise.resolve(30)
];
Promise.all(promises)
    .then(results => {
        console.log("All results:", results); // [10, 20, 30]
    })
    .catch(error => console.error("One failed:", error));

// Promise.race - first to complete
const racePromises = [
    new Promise(resolve => setTimeout(() => resolve("First"), 100)),
    new Promise(resolve => setTimeout(() => resolve("Second"), 200))
];
Promise.race(racePromises)
    .then(result => console.log("Winner:", result)); // First

// Promise.allSettled - all complete, regardless of success
const settledPromises = [
    Promise.resolve(10),
    Promise.reject(new Error("Failed")),
    Promise.resolve(30)
];
Promise.allSettled(settledPromises)
    .then(results => {
        results.forEach(result => {
            if (result.status === 'fulfilled') {
                console.log("Success:", result.value);
            } else {
                console.log("Error:", result.reason.message);
            }
        });
    });

Promises handle asynchronous operations. .then() handles success, .catch() handles errors. .finally() runs regardless. Promise.all() waits for all promises to resolve. Promise.race() resolves with the first promise to complete

7.2 Async/Await

Syntactic sugar over Promises. Makes asynchronous code look synchronous.

  • async – Declares an async function
  • await – Waits for a Promise to resolve
  • Error handling – Use try...catch
  • Concurrency – Multiple async operations
  • Readability – Linear code flow

Code Example:

// Simulating API calls
function fetchBirds() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(["Eagle", "Sparrow", "Hawk"]);
        }, 1000);
    });
}

function fetchBirdCounts() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve({ Eagle: 3, Sparrow: 10, Hawk: 5 });
        }, 500);
    });
}

// Basic async/await
async function getBirdData() {
    try {
        console.log("Fetching data...");
        const birds = await fetchBirds();
        console.log("Birds:", birds);
        
        const counts = await fetchBirdCounts();
        console.log("Counts:", counts);
        
        return { birds, counts };
    } catch (error) {
        console.error("Error:", error.message);
        throw error;
    }
}

// Usage
async function main() {
    try {
        const data = await getBirdData();
        console.log("Combined data:", data);
    } catch (error) {
        console.error("Failed to get bird data:", error.message);
    }
}
main();

// Concurrent async operations
async function getConcurrentData() {
    try {
        const [birds, counts] = await Promise.all([
            fetchBirds(),
            fetchBirdCounts()
        ]);
        console.log("Concurrent results:", { birds, counts });
        return { birds, counts };
    } catch (error) {
        console.error("Concurrent error:", error.message);
    }
}

// Sequential vs Concurrent
async function compareApproaches() {
    // Sequential (slower)
    const start1 = Date.now();
    const birds = await fetchBirds();
    const counts = await fetchBirdCounts();
    const time1 = Date.now() - start1;
    console.log(`Sequential: ${time1}ms`);
    
    // Concurrent (faster)
    const start2 = Date.now();
    const [birds2, counts2] = await Promise.all([
        fetchBirds(),
        fetchBirdCounts()
    ]);
    const time2 = Date.now() - start2;
    console.log(`Concurrent: ${time2}ms`);
}

// Async IIFE (Immediately Invoked Function Expression)
(async () => {
    const data = await getBirdData();
    console.log("IIFE result:", data);
})();

// Top-level await (ES2022+)
// const data = await getBirdData();

async functions return Promises. await pauses execution until the Promise resolves. try...catch handles errors. Promise.all() enables concurrent operations. Async code becomes linear and readable.

7.3 Error Handling (try/catch/finally)

Error handling manages runtime errors gracefully.

  • The try block contains code that may throw an error or exception, allowing the program to handle the problem gracefully.
  • catch – Handles thrown errors
  • finally – Always executes
  • throw – Throws an error
  • Custom errors – Extend Error class

Code Example:

// Basic try/catch/finally
function riskyOperation(shouldFail = false) {
    if (shouldFail) {
        throw new Error("Operation failed");
    }
    return "Success!";
}

try {
    console.log("Starting operation...");
    const result = riskyOperation(false);
    console.log("Result:", result);
} catch (error) {
    console.error("Caught error:", error.message);
} finally {
    console.log("Cleanup always runs");
}

// Multiple catch blocks
try {
    const data = JSON.parse("invalid json");
} catch (error) {
    if (error instanceof SyntaxError) {
        console.log("Syntax error:", error.message);
    } else {
        console.log("Other error:", error.message);
    }
}

// Custom error types
class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = "ValidationError";
        this.field = field;
    }
}

class DatabaseError extends Error {
    constructor(message, code) {
        super(message);
        this.name = "DatabaseError";
        this.code = code;
    }
}

function validateBird(data) {
    if (!data.species || data.species.length === 0) {
        throw new ValidationError("Species is required", "species");
    }
    if (data.count < 0) {
        throw new ValidationError("Count cannot be negative", "count");
    }
    if (data.count > 1000) {
        throw new DatabaseError("Maximum count exceeded", "COUNT_LIMIT");
    }
    return true;
}

// Handling specific errors
try {
    validateBird({ species: "", count: 10 });
} catch (error) {
    if (error instanceof ValidationError) {
        console.log(`Validation error in field '${error.field}': ${error.message}`);
    } else if (error instanceof DatabaseError) {
        console.log(`Database error (${error.code}): ${error.message}`);
    } else {
        console.log(`Unknown error: ${error.message}`);
    }
}

// Error handling in async functions
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        return await response.json();
    } catch (error) {
        console.error("Fetch error:", error.message);
        throw new Error("Failed to fetch data: " + error.message);
    } finally {
        console.log("Fetch attempt completed");
    }
}

// Debugging with error stack
function depth3() {
    throw new Error("Error at depth 3");
}
function depth2() {
    depth3();
}
function depth1() {
    depth2();
}
try {
    depth1();
} catch (error) {
    console.log("Stack trace:");
    console.log(error.stack);
}

try contains code that might throw an error. catch handles the error with the error object. finally runs regardless of success or failure. throw creates and throws errors. Custom errors extend Error for specific error types.

7.4 Fetch API / Axios

Fetch is built-in for HTTP requests. Axios is a third-party library with additional features.

Fetch API:

  • Built-in browser API
  • Promise-based
  • Returns Response object
  • Error handling requires checking response.ok

Axios:

  • Third-party library
  • Promise-based
  • Automatic JSON parsing
  • Built-in error handling
  • Request/response interceptors

Code Example:

// ---- FETCH API ----
async function fetchBirdData() {
    try {
        // GET request
        const response = await fetch('https://api.example.com/birds');
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
        }
        const data = await response.json();
        console.log("Birds:", data);
        return data;
    } catch (error) {
        console.error("Fetch error:", error.message);
        return null;
    }
}

// POST with Fetch
async function createBird(bird) {
    try {
        const response = await fetch('https://api.example.com/birds', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': 'Bearer token123'
            },
            body: JSON.stringify(bird)
        });
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
        }
        const data = await response.json();
        console.log("Created:", data);
        return data;
    } catch (error) {
        console.error("Create error:", error.message);
        return null;
    }
}

// ---- AXIOS (installation: npm install axios) ----
// const axios = require('axios');

// GET with Axios
async function axiosGetBirds() {
    try {
        const response = await axios.get('https://api.example.com/birds', {
            params: { limit: 10, sort: 'name' },
            headers: { 'Authorization': 'Bearer token123' }
        });
        console.log("Axios GET:", response.data);
        return response.data;
    } catch (error) {
        console.error("Axios error:", error.message);
        return null;
    }
}

// POST with Axios
async function axiosCreateBird(bird) {
    try {
        const response = await axios.post('https://api.example.com/birds', bird, {
            headers: { 'Authorization': 'Bearer token123' }
        });
        console.log("Axios POST:", response.data);
        return response.data;
    } catch (error) {
        console.error("Axios error:", error.message);
        if (error.response) {
            console.log("Response data:", error.response.data);
            console.log("Status:", error.response.status);
        }
        return null;
    }
}

// ---- Helper Functions ----
// URL encode parameters
function buildUrl(base, params) {
    const url = new URL(base);
    Object.entries(params).forEach(([key, value]) => {
        url.searchParams.append(key, value);
    });
    return url.toString();
}

// Retry logic
async function fetchWithRetry(url, retries = 3) {
    let attempt = 0;
    while (attempt < retries) {
        try {
            const response = await fetch(url);
            if (response.ok) {
                return await response.json();
            }
        } catch (error) {
            console.log(`Attempt ${attempt + 1} failed:`, error.message);
        }
        attempt++;
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
    throw new Error(`Failed after ${retries} attempts`);
}

Fetch returns a Response object that requires json() to parse. Axios automatically parses JSON. Both support GET, POST, PUT, DELETE. Error handling checks response.ok for Fetch or catches errors for Axios. Headers and body are included in request options.

7.5 Generators & Iterators

Generators (function*) can yield multiple values, pausing execution. Iterators implement next().

  • Generator function – function* syntax
  • yield – Pauses and returns value
  • next() – Resumes execution
  • return() – Ends generator
  • throw() – Throws error into generator
  • Iterable protocol – Objects with [Symbol.iterator]

Code Example:

// ---- BASIC GENERATOR ----
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().done);  // true

// ---- GENERATOR WITH LOOP ----
function* birdGenerator(birds) {
    for (const bird of birds) {
        yield bird;
    }
}

const birds = ["Eagle", "Sparrow", "Hawk"];
const birdGen = birdGenerator(birds);
console.log(birdGen.next().value); // Eagle
console.log(birdGen.next().value); // Sparrow
console.log(birdGen.next().value); // Hawk

// ---- INFINITE GENERATOR ----
function* infiniteCounter() {
    let count = 0;
    while (true) {
        yield count++;
    }
}

const counter = infiniteCounter();
console.log(counter.next().value); // 0
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2

// ---- GENERATOR WITH INPUT ----
function* interactiveGenerator() {
    const name = yield "What is your name?";
    const bird = yield "What is your favorite bird?";
    return `${name} likes ${bird}`;
}

const interactive = interactiveGenerator();
console.log(interactive.next().value); // "What is your name?"
console.log(interactive.next("Alice").value); // "What is your favorite bird?"
console.log(interactive.next("Eagle").value); // "Alice likes Eagle"

// ---- CUSTOM ITERABLE ----
class BirdCollection {
    constructor(birds) {
        this.birds = birds;
    }
    
    [Symbol.iterator]() {
        let index = 0;
        const birds = this.birds;
        return {
            next() {
                if (index < birds.length) {
                    return { value: birds[index++], done: false };
                }
                return { done: true };
            }
        };
    }
}

const collection = new BirdCollection(["Eagle", "Sparrow", "Hawk"]);
for (const bird of collection) {
    console.log(bird);
}

// ---- BUILT-IN ITERABLES ----
// Arrays
const array = [1, 2, 3];
const arrIterator = array[Symbol.iterator]();
console.log(arrIterator.next().value); // 1

// Strings
const str = "ABC";
const strIterator = str[Symbol.iterator]();
console.log(strIterator.next().value); // "A"

// Maps
const map = new Map([["a", 1], ["b", 2]]);
for (const [key, value] of map) {
    console.log(key, value);
}

Generators use function* syntax. yield pauses execution and returns a value. next() resumes execution. Generators can be infinite. The iterable protocol defines [Symbol.iterator] returning an iterator with next().

7.6 Destructuring

Extract values from arrays or objects into variables.

  • Array destructuring – Extract array elements
  • Object destructuring – Extract object properties
  • Nested destructuring – Extract from nested structures
  • Default values – Provide fallback values
  • Rest operator – Collect remaining elements

Code Example:

// ---- ARRAY DESTRUCTURING ----
const birds = ["Eagle", "Sparrow", "Hawk", "Robin"];

// Basic destructuring
const [first, second] = birds;
console.log(first, second); // Eagle, Sparrow

// Skip elements
const [, , third] = birds;
console.log(third); // Hawk

// Rest operator
const [head, ...tail] = birds;
console.log(head, tail); // Eagle, ["Sparrow", "Hawk", "Robin"]

// Default values
const [a = "Unknown", b = "Unknown"] = [];
console.log(a, b); // Unknown, Unknown

// Nested array destructuring
const nested = [1, [2, 3]];
const [one, [two, three]] = nested;
console.log(one, two, three); // 1, 2, 3

// ---- OBJECT DESTRUCTURING ----
const bird = {
    species: "Eagle",
    weight: 4500,
    color: "brown",
    habitat: "mountains"
};

// Basic destructuring
const { species, weight } = bird;
console.log(species, weight); // Eagle, 4500

// Rename variables
const { species: birdName, weight: birdWeight } = bird;
console.log(birdName, birdWeight); // Eagle, 4500

// Default values
const { wingspan = "unknown" } = bird;
console.log(wingspan); // unknown

// Rest operator
const { species: name, ...rest } = bird;
console.log(name, rest); // Eagle, { weight: 4500, color: "brown", habitat: "mountains" }

// Nested object destructuring
const complexBird = {
    name: "Eagle",
    details: {
        weight: 4500,
        wingspan: 2.3,
        colors: ["brown", "white"]
    }
};
const { details: { weight: w, wingspan: ws, colors: [primary, secondary] } } = complexBird;
console.log(w, ws, primary, secondary); // 4500, 2.3, brown, white

// ---- FUNCTION PARAMETER DESTRUCTURING ----
function displayBird({ species, weight, color = "unknown" }) {
    console.log(`${species} weighs ${weight}g and is ${color}`);
}
displayBird({ species: "Sparrow", weight: 25 }); // Sparrow weighs 25g and is unknown

function sumFirstTwo([a, b]) {
    return a + b;
}
console.log(sumFirstTwo([1, 2, 3])); // 3

// ---- SWAPPING VARIABLES ----
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2, 1

Destructuring provides a concise way to extract values. Array destructuring uses positions. Object destructuring uses property names. Default values handle missing data. The rest operator collects remaining elements.

7.7 Spread & Rest Operators

Spread (...) expands elements. Rest collects multiple arguments into an array.

Spread Operator (...):

  • Expands arrays into individual elements
  • Copies arrays and objects
  • Concatenates arrays
  • Spread in function calls

Rest Operator (...):

  • Collects remaining arguments into an array
  • Used in function parameters
  • Used in destructuring

Code Example:

// ---- SPREAD OPERATOR ----

// Spread in arrays
const birds = ["Eagle", "Sparrow"];
const moreBirds = ["Hawk", ...birds, "Robin"];
console.log(moreBirds); // ["Hawk", "Eagle", "Sparrow", "Robin"]

// Copy array
const copy = [...birds];
console.log(copy); // ["Eagle", "Sparrow"]

// Spread in function calls
function sum(a, b, c) {
    return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

// Spread in objects
const bird = { species: "Eagle", weight: 4500 };
const detailedBird = { ...bird, color: "brown", habitat: "mountains" };
console.log(detailedBird); // { species: "Eagle", weight: 4500, color: "brown", habitat: "mountains" }

// Object copy
const birdCopy = { ...bird };
console.log(birdCopy); // { species: "Eagle", weight: 4500 }

// Merge objects
const birdInfo = { species: "Eagle", weight: 4500 };
const location = { habitat: "mountains", region: "north" };
const merged = { ...birdInfo, ...location };
console.log(merged); // { species: "Eagle", weight: 4500, habitat: "mountains", region: "north" }

// ---- REST OPERATOR ----

// Rest in function parameters
function collectBirds(...birds) {
    console.log(birds); // ["Eagle", "Sparrow", "Hawk"]
    return birds.length;
}
console.log(collectBirds("Eagle", "Sparrow", "Hawk")); // 3

// Rest with other parameters
function processBirds(prefix, ...birds) {
    return birds.map(bird => `${prefix} ${bird}`);
}
console.log(processBirds("Bird:", "Eagle", "Sparrow", "Hawk"));
// ["Bird: Eagle", "Bird: Sparrow", "Bird: Hawk"]

// Rest in destructuring
const [first, ...rest] = ["Eagle", "Sparrow", "Hawk", "Robin"];
console.log(first, rest); // Eagle, ["Sparrow", "Hawk", "Robin"]

const { species, ...details } = { species: "Eagle", weight: 4500, color: "brown" };
console.log(species, details); // Eagle, { weight: 4500, color: "brown" }

// ---- PRACTICAL EXAMPLES ----

// Math.max with spread
const values = [5, 3, 9, 1, 7];
console.log(Math.max(...values)); // 9

// Convert NodeList to array (browser)
// const elements = document.querySelectorAll('div');
// const elementArray = [...elements];

// Create unique array
const duplicates = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(duplicates)];
console.log(unique); // [1, 2, 3, 4]

Spread expands elements where multiple arguments or elements are expected. Rest collects elements into an array. Spread copies arrays and objects shallowly. Rest is used in function parameters and destructuring.

7.8 Optional Chaining & Nullish Coalescing

Optional chaining (?.) stops evaluation if left side is null/undefined. Nullish coalescing (??) provides fallback only for null/undefined.

Optional Chaining (?.):

  • Safely access nested properties
  • Returns undefined if chain fails
  • Works with function calls ?.()
  • Works with bracket notation ?.[]

Nullish Coalescing (??):

  • Provides default for null or undefined only
  • Unlike ||, doesn’t fail for other falsy values
  • Used with optional chaining

Code Example:

// ---- OPTIONAL CHAINING ----

// Basic usage
const user = {
    name: "Alice",
    profile: {
        age: 25,
        address: {
            city: "NYC"
        }
    }
};

// Without optional chaining
const city = user && user.profile && user.profile.address && user.profile.address.city;
console.log(city); // NYC

// With optional chaining
const city2 = user?.profile?.address?.city;
console.log(city2); // NYC

// Missing property
const country = user?.profile?.address?.country;
console.log(country); // undefined

// Optional chaining with arrays
const birds = ["Eagle", "Sparrow", "Hawk"];
const firstBird = birds?.[0];
console.log(firstBird); // Eagle

const invalid = null;
const invalidBird = invalid?.[0];
console.log(invalidBird); // undefined

// Optional chaining with function calls
const obj = {
    greet() {
        return "Hello";
    }
};
console.log(obj.greet?.()); // Hello
console.log(obj.sayGoodbye?.()); // undefined

// ---- NULLISH COALESCING ----

// Basic usage
const name = null;
console.log(name ?? "Guest"); // Guest

// Comparison with OR (||)
const count = 0;
console.log(count || 10); // 10 (OR treats 0 as falsy)
console.log(count ?? 10); // 0 (?? only treats null/undefined as nullish)

const emptyString = "";
console.log(emptyString || "Default"); // Default
console.log(emptyString ?? "Default"); // "" (empty string is not nullish)

// Combined with optional chaining
const user2 = {
    name: "Alice",
    age: 0
};
const userAge = user2?.age ?? 18;
console.log(userAge); // 0 (0 is not nullish)

// Default values for nested properties
const deepValue = user2?.profile?.age ?? "Unknown";
console.log(deepValue); // Unknown

// ---- PRACTICAL EXAMPLES ----

// Safe object access
function getBirdInfo(bird) {
    return {
        species: bird?.species ?? "Unknown",
        count: bird?.count ?? 0,
        weight: bird?.weight ?? "Not available"
    };
}

console.log(getBirdInfo({ species: "Eagle", count: 3 }));
// { species: "Eagle", count: 3, weight: "Not available" }

console.log(getBirdInfo(null));
// { species: "Unknown", count: 0, weight: "Not available" }

// Safe array access
function getFirstBird(birds) {
    return birds?.[0] ?? "No birds";
}

console.log(getFirstBird(["Eagle", "Sparrow"])); // Eagle
console.log(getFirstBird([])); // No birds
console.log(getFirstBird(null)); // No birds

// Safe function call
function safeExecute(obj, method, ...args) {
    return obj?.[method]?.(...args) ?? "Method not available";
}

const calculator = {
    add(a, b) { return a + b; }
};
console.log(safeExecute(calculator, "add", 5, 3)); // 8
console.log(safeExecute(calculator, "subtract", 5, 3)); // Method not available

Optional chaining ?. short-circuits if the left side is null or undefined. It can be chained indefinitely. Nullish coalescing ?? only provides a fallback for null or undefined, not other falsy values. They are often used together for safe data access.

7.9 Modules (ESM vs CommonJS)

Modules allow code organization and reuse across files.

ESM (ECMAScript Modules):

  • import and export syntax
  • Browser and Node.js (ESM)
  • Static imports/exports
  • Live bindings
  • Tree-shakeable

CommonJS:

  • require() and module.exports
  • Node.js default
  • Dynamic imports
  • Synchronous loading

Code Example:

// ---- ESM ----

// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;

export default function sum(...numbers) {
    return numbers.reduce((acc, n) => acc + n, 0);
}

// main.mjs
import sum, { add, subtract, multiply as mul } from './math.mjs';

console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2
console.log(mul(5, 3)); // 15
console.log(sum(1, 2, 3, 4)); // 10

// Namespace import
import * as math from './math.mjs';
console.log(math.add(5, 3)); // 8

// Dynamic import
async function loadModule() {
    const module = await import('./math.mjs');
    console.log(module.add(5, 3)); // 8
}
loadModule();

// ---- COMMONJS ----

// math.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function sum(...numbers) {
    return numbers.reduce((acc, n) => acc + n, 0);
}

module.exports = {
    add,
    subtract,
    multiply,
    sum
};

// Or individual exports
exports.add = add;
exports.subtract = subtract;

// main.js
const math = require('./math.js');
console.log(math.add(5, 3)); // 8

const { add, subtract } = require('./math.js');
console.log(add(5, 3)); // 8

// ---- PRACTICAL EXAMPLES ----

// Exporting classes
// bird.mjs
export class Bird {
    constructor(species) {
        this.species = species;
    }
    speak() {
        console.log(`${this.species} chirps`);
    }
}
export const createBird = (species) => new Bird(species);

// main.mjs
import Bird, { createBird } from './bird.mjs';
const sparrow = new Bird("Sparrow");
sparrow.speak(); // Sparrow chirps

// Exporting constants
export const MAX_BIRDS = 100;
export const BIRD_TYPES = ["Eagle", "Sparrow", "Hawk"];

// Re-exporting
// index.mjs
export { default as Bird } from './bird.mjs';
export { add, subtract } from './math.mjs';

// Package.json for ESM
{
    "type": "module"
}

ESM uses import/export with static analysis. CommonJS uses require/module.exports with dynamic loading. ESM is the standard for modern JavaScript. Node.js supports both via file extensions or package.json settings.

7.10 Template Literals, Default Parameters, Arrow Functions

Modern JavaScript features for cleaner code.

Template Literals:

  • Use backticks ``
  • String interpolation ${}
  • Multi-line strings
  • Tagged templates

Default Parameters:

  • Provide default values
  • Evaluated at call time
  • Can reference other parameters
  • undefined triggers default

Arrow Functions:

  • Concise syntax () => {}
  • Lexical this binding
  • No arguments object
  • Cannot be used as constructors

Code Example:

// ---- TEMPLATE LITERALS ----

// Basic interpolation
const name = "Alice";
const greeting = `Hello, ${name}!`;
console.log(greeting); // Hello, Alice!

// Multi-line strings
const multiline = `
    This is a multi-line
    string in JavaScript.
    It preserves whitespace.
`;
console.log(multiline);

// Expressions in template literals
const a = 10, b = 5;
console.log(`${a} + ${b} = ${a + b}`); // 10 + 5 = 15

// Tagged templates
function tag(strings, ...values) {
    console.log(strings); // ["Hello ", "! You are ", " years old."]
    console.log(values); // ["Alice", 25]
    return strings.reduce((result, str, i) => {
        return result + str + (values[i] || '');
    }, '');
}
const tagged = tag`Hello ${name}! You are ${25} years old.`;
console.log(tagged); // Hello Alice! You are 25 years old.

// ---- DEFAULT PARAMETERS ----

// Basic default
function greet(name = "Guest") {
    return `Hello, ${name}!`;
}
console.log(greet()); // Hello, Guest!
console.log(greet("Alice")); // Hello, Alice!

// Default with expressions
function calculate(value, multiplier = value * 2) {
    return value * multiplier;
}
console.log(calculate(5)); // 50 (5 * 10)

// Default with previous parameters
function describeBird(species, count = 0, message = `${species}: ${count}`) {
    return message;
}
console.log(describeBird("Eagle", 3)); // Eagle: 3

// ---- ARROW FUNCTIONS ----

// Basic arrow
const add = (a, b) => a + b;
console.log(add(5, 3)); // 8

// Single parameter (no parentheses needed)
const double = x => x * 2;
console.log(double(5)); // 10

// No parameters
const greetWorld = () => "Hello, World!";
console.log(greetWorld()); // Hello, World!

// Block body
const process = (a, b) => {
    const sum = a + b;
    const product = a * b;
    return { sum, product };
};
console.log(process(5, 3)); // { sum: 8, product: 15 }

// Lexical this
const bird = {
    name: "Eagle",
    // Arrow function (lexical this)
    arrowSpeak: () => {
        console.log(this.name); // undefined (lexical this)
    },
    // Regular function
    regularSpeak() {
        console.log(this.name); // Eagle (method this)
    },
    // Arrow in method (uses outer this)
    delayedSpeak() {
        setTimeout(() => {
            console.log(this.name); // Eagle (lexical this from method)
        }, 100);
    }
};
bird.arrowSpeak(); // undefined
bird.regularSpeak(); // Eagle
bird.delayedSpeak(); // Eagle

Template literals support string interpolation and multi-line strings. Default parameters provide fallback values. Arrow functions are concise and maintain lexical this binding. Each feature improves code readability and maintainability.

7.11 Event Loop Deep Dive (Microtask vs Macrotask)

Microtasks (Promise.then, queueMicrotask) execute before macrotasks (setTimeout, setInterval, I/O).

Macrotasks (Task Queue):

  • setTimeout, setInterval
  • setImmediate
  • I/O operations
  • UI rendering
  • Event callbacks

Microtasks (Microtask Queue):

  • Promise.then(), Promise.catch()
  • queueMicrotask()
  • MutationObserver
  • process.nextTick() (Node.js)

Execution Order:

  1. Run all synchronous code
  2. Execute all microtasks
  3. Execute one macrotask
  4. Repeat (microtasks after each macrotask)

Code Example:

// ---- BASIC EVENT LOOP DEMONSTRATION ----
console.log("1: Start");

setTimeout(() => {
    console.log("6: setTimeout (macrotask)");
}, 0);

Promise.resolve().then(() => {
    console.log("4: Promise.then (microtask)");
});

queueMicrotask(() => {
    console.log("5: queueMicrotask (microtask)");
});

console.log("2: Middle");

setTimeout(() => {
    console.log("7: Another setTimeout (macrotask)");
}, 0);

console.log("3: End");

// Output order:
// 1: Start
// 2: Middle
// 3: End
// 4: Promise.then (microtask)
// 5: queueMicrotask (microtask)
// 6: setTimeout (macrotask)
// 7: Another setTimeout (macrotask)

// ---- EVENT LOOP IN ACTION ----
function eventLoopDemo() {
    console.log("A: Sync start");
    
    setTimeout(() => {
        console.log("E: setTimeout (macro)");
    }, 0);
    
    Promise.resolve()
        .then(() => {
            console.log("C: Promise 1 (micro)");
            return 10;
        })
        .then(() => {
            console.log("D: Promise 2 (micro)");
        });
    
    console.log("B: Sync end");
}
eventLoopDemo();

// Output:
// A: Sync start
// B: Sync end
// C: Promise 1 (micro)
// D: Promise 2 (micro)
// E: setTimeout (macro)

// ---- MICROTASK QUEUE PROCESSING ----
function microtaskFlood() {
    let count = 0;
    function addMicrotask() {
        queueMicrotask(() => {
            count++;
            if (count < 10) {
                addMicrotask();
            } else {
                console.log(`Processed ${count} microtasks`);
            }
        });
    }
    console.log("Starting microtask flood");
    addMicrotask();
    console.log("Microtasks queued");
}
microtaskFlood();

// ---- MACROTASK AND MICROTASK INTERLEAVING ----
function interleaveExample() {
    console.log("Start");
    
    setTimeout(() => {
        console.log("Macro 1");
        
        Promise.resolve().then(() => {
            console.log("Micro 1 (from Macro 1)");
        });
        
        setTimeout(() => {
            console.log("Macro 2 (nested)");
        }, 0);
    }, 0);
    
    Promise.resolve().then(() => {
        console.log("Micro 2 (initial)");
    });
    
    console.log("End");
}
// Output: Start, End, Micro 2, Macro 1, Micro 1, Macro 2

Microtasks are executed after each macrotask. The microtask queue is emptied before the next macrotask. This ensures Promises are handled promptly. Event loop order: sync code → microtasks → macrotasks → microtasks → macrotasks (repeat).

7.12 Web Workers & Service Workers

Web Workers run scripts in background threads. Service Workers intercept network requests and enable offline PWAs.

Web Workers:

  • Background threads
  • No DOM access
  • Message passing
  • Parallel processing
  • Long-running tasks

Service Workers:

  • Background scripts
  • Network interception
  • Offline caching
  • Push notifications
  • PWA support

Code Example:

// ---- WEB WORKER ----

// worker.js
self.onmessage = function(e) {
    console.log('Worker received:', e.data);
    const result = e.data * 2;
    self.postMessage(result);
};

// main.js
const worker = new Worker('worker.js');

worker.onmessage = function(e) {
    console.log('Main received:', e.data);
};

worker.postMessage(10);
// Worker sends: 20

// ---- SERVICE WORKER ----

// sw.js
const CACHE_NAME = 'bird-cache-v1';
const urlsToCache = [
    '/',
    '/index.html',
    '/app.js',
    '/style.css'
];

// Install event
self.addEventListener('install', event => {
    console.log('Service Worker installing');
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(urlsToCache))
    );
});

// Activate event
self.addEventListener('activate', event => {
    console.log('Service Worker activating');
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(name => {
                    if (name !== CACHE_NAME) {
                        return caches.delete(name);
                    }
                })
            );
        })
    );
});

// Fetch event - network first with cache fallback
self.addEventListener('fetch', event => {
    event.respondWith(
        fetch(event.request)
            .then(response => {
                // Network successful, cache the response
                const responseClone = response.clone();
                caches.open(CACHE_NAME)
                    .then(cache => {
                        cache.put(event.request, responseClone);
                    });
                return response;
            })
            .catch(() => {
                // Network failed, return cached version
                return caches.match(event.request);
            })
    );
});

// ---- REGISTER SERVICE WORKER ----
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(registration => {
            console.log('SW registered:', registration);
        })
        .catch(error => {
            console.log('SW registration failed:', error);
        });
}

// ---- MESSAGE PASSING WITH SERVICE WORKER ----
// sw.js
self.addEventListener('message', event => {
    console.log('SW received:', event.data);
    event.ports[0].postMessage('Response from SW');
});

// main.js
const messageChannel = new MessageChannel();
navigator.serviceWorker.ready.then(registration => {
    registration.active.postMessage('Hello SW', [messageChannel.port2]);
});
messageChannel.port1.onmessage = event => {
    console.log('Response:', event.data);
};

Web Workers run JavaScript in separate threads. Service Workers run as network proxies. Both use message passing for communication. Service Workers enable offline caching and PWA features.

7.13 WebSockets

Full-duplex real-time communication between client and server.

  • Persistent connection – Open connection
  • Bi-directional – Both sides can send messages
  • Real-time – Low latency
  • Protocol – ws:// or wss://
  • Events – open, message, close, error

Code Example:

// ---- CLIENT-SIDE WEBSOCKET ----

// Create WebSocket connection
const ws = new WebSocket('wss://echo.websocket.org');

// Connection opened
ws.addEventListener('open', event => {
    console.log('Connected to server');
    
    // Send a message to the server
    ws.send('Hello, Server!');
});

// Listen for messages
ws.addEventListener('message', event => {
    console.log('Message from server:', event.data);
});

// Connection closed
ws.addEventListener('close', event => {
    console.log('Connection closed');
});

// Connection error
ws.addEventListener('error', event => {
    console.error('WebSocket error:', event);
});

// ---- REAL-TIME CHAT EXAMPLE ----

class ChatClient {
    constructor(url) {
        this.ws = null;
        this.url = url;
        this.messageListeners = [];
        this.connectionListeners = [];
    }
    
    connect() {
        this.ws = new WebSocket(this.url);
        
        this.ws.onopen = () => {
            console.log('Connected');
            this.connectionListeners.forEach(fn => fn(true));
        };
        
        this.ws.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                this.messageListeners.forEach(fn => fn(data));
            } catch (e) {
                console.error('Invalid message:', event.data);
            }
        };
        
        this.ws.onclose = () => {
            console.log('Disconnected');
            this.connectionListeners.forEach(fn => fn(false));
            // Auto-reconnect after 3 seconds
            setTimeout(() => this.connect(), 3000);
        };
        
        this.ws.onerror = (error) => {
            console.error('Error:', error);
        };
    }
    
    sendMessage(message) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify({
                type: 'message',
                content: message,
                timestamp: new Date().toISOString()
            }));
        } else {
            console.log('Not connected');
        }
    }
    
    onMessage(callback) {
        this.messageListeners.push(callback);
    }
    
    onConnection(callback) {
        this.connectionListeners.push(callback);
    }
    
    disconnect() {
        if (this.ws) {
            this.ws.close();
        }
    }
}

// Usage
const chat = new ChatClient('wss://echo.websocket.org');
chat.onMessage((data) => {
    console.log('New message:', data);
});
chat.onConnection((isConnected) => {
    console.log('Connection status:', isConnected);
});
chat.connect();

// Send message after connection
setTimeout(() => {
    chat.sendMessage('Hello from chat client!');
}, 1000);

// ---- SERVER-SIDE WEBSOCKET (Node.js + ws) ----
// const WebSocket = require('ws');
// const wss = new WebSocket.Server({ port: 8080 });

// wss.on('connection', (ws) => {
//     console.log('Client connected');
//     
//     ws.on('message', (message) => {
//         console.log('Received:', message.toString());
//         // Echo back
//         ws.send(`Echo: ${message}`);
//     });
//     
//     ws.send('Welcome to the WebSocket server!');
// });

// console.log('WebSocket server running on ws://localhost:8080');

WebSockets establish a persistent connection. Both client and server can send messages. The connection stays open for real-time communication. Events handle lifecycle and messages. Auto-reconnect handles connection drops.

7.14 Debouncing / Throttling

Techniques to limit the rate of function execution, useful for scroll, resize, or input events.

Debouncing:

  • Delays execution until after a delay
  • Only executes once for a rapid sequence
  • Good for search input, auto-save
  • Resets timer on each call

Throttling:

  • Limits execution rate
  • Executes at most once per interval
  • Good for scroll, resize, mousemove
  • Maintains regular execution

Code Example:

// ---- DEBOUNCE ----
function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// Example: Search input
const searchInput = document.querySelector('#search');
const searchResults = document.querySelector('#results');

function performSearch(query) {
    console.log('Searching for:', query);
    // Simulate API call
    // searchResults.textContent = `Results for "${query}"`;
}

// Create debounced version
const debouncedSearch = debounce(performSearch, 300);

// Attach to input event
searchInput?.addEventListener('input', (e) => {
    debouncedSearch(e.target.value);
});

// ---- THROTTLE ----
function throttle(func, limit) {
    let inThrottle = false;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => {
                inThrottle = false;
            }, limit);
        }
    };
}

// Example: Scroll handler
function handleScroll() {
    console.log('Scroll position:', window.scrollY);
}

const throttledScroll = throttle(handleScroll, 200);

// Attach to scroll event
window.addEventListener('scroll', throttledScroll);

// ---- LEADING DEBOUNCE (executes immediately) ----
function debounceLeading(func, delay) {
    let timeoutId;
    return function(...args) {
        if (timeoutId === undefined) {
            func.apply(this, args);
        }
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            timeoutId = undefined;
        }, delay);
    };
}

// ---- TRAILING THROTTLE (executes at end) ----
function throttleTrailing(func, limit) {
    let timeoutId;
    let lastArgs;
    return function(...args) {
        lastArgs = args;
        if (timeoutId === undefined) {
            func.apply(this, args);
            timeoutId = setTimeout(() => {
                timeoutId = undefined;
                if (lastArgs && lastArgs !== args) {
                    func.apply(this, lastArgs);
                }
            }, limit);
        }
    };
}

// ---- PRACTICAL: AUTO-SAVE ----
function autoSave(data) {
    console.log('Saving data:', data);
    // Send to server
}

const debouncedSave = debounce(autoSave, 1000);

// Usage in editor
document.querySelector('#editor')?.addEventListener('input', (e) => {
    debouncedSave(e.target.value);
});

// ---- PRACTICAL: RESIZE HANDLER ----
function handleResize() {
    console.log('Window resized:', window.innerWidth, 'x', window.innerHeight);
}

const throttledResize = throttle(handleResize, 250);
window.addEventListener('resize', throttledResize);

Debouncing delays execution until activity stops. Throttling limits execution rate. Both prevent excessive function calls. Use debounce for search inputs, throttling for scroll events.

7.15 Performance Optimization

Techniques to improve JavaScript performance.

Code-Level Optimizations:

  • Use efficient algorithms
  • Minimize DOM operations
  • Debounce/throttle events
  • Use requestAnimationFrame for animations
  • Avoid memory leaks

Build Optimizations:

  • Minification
  • Tree shaking
  • Code splitting
  • Lazy loading
  • Bundle optimization

Runtime Optimizations:

  • Caching
  • Use Map and Set for lookups
  • Use Web Workers for heavy tasks
  • Use requestIdleCallback for non-critical tasks

Code Example:

// ---- DOM OPTIMIZATION ----

// Bad: Multiple DOM updates
function badUpdate(items) {
    for (const item of items) {
        const div = document.createElement('div');
        div.textContent = item;
        document.body.appendChild(div);
    }
}

// Good: Batch DOM updates
function goodUpdate(items) {
    const fragment = document.createDocumentFragment();
    for (const item of items) {
        const div = document.createElement('div');
        div.textContent = item;
        fragment.appendChild(div);
    }
    document.body.appendChild(fragment);
}

// ---- CACHING ----

// Bad: Repeated DOM access
function badAccess() {
    for (let i = 0; i < 100; i++) {
        document.getElementById('count').textContent = i;
    }
}

// Good: Cache DOM reference
function goodAccess() {
    const el = document.getElementById('count');
    for (let i = 0; i < 100; i++) {
        el.textContent = i;
    }
}

// ---- ARRAY OPTIMIZATION ----

// Bad: Using forEach for large arrays
function badSum(arr) {
    let sum = 0;
    arr.forEach(n => sum += n);
    return sum;
}

// Good: Use for loop for large arrays
function goodSum(arr) {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}

// ---- LAZY LOADING ----

// Lazy load image
function lazyLoadImage(imageElement) {
    if ('loading' in HTMLImageElement.prototype) {
        imageElement.loading = 'lazy';
    } else {
        // Fallback using IntersectionObserver
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    img.src = img.dataset.src;
                    observer.unobserve(img);
                }
            });
        });
        observer.observe(imageElement);
    }
}

// ---- REQUESTANIMATIONFRAME ----

// Animation loop
let startTime = null;

function animate(timestamp) {
    if (!startTime) startTime = timestamp;
    const progress = timestamp - startTime;
    
    // Update animation
    const element = document.querySelector('.animated');
    if (element) {
        element.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;
    }
    
    if (progress < 2000) {
        requestAnimationFrame(animate);
    }
}

// Start animation
requestAnimationFrame(animate);

// ---- CACHE LOOKUPS ----

// Bad: Repeated calculation
function badFilter(items, threshold) {
    return items.filter(item => {
        const result = expensiveCalculation(item);
        return result > threshold;
    });
}

// Good: Cache results
function goodFilter(items, threshold) {
    const cache = new Map();
    return items.filter(item => {
        if (!cache.has(item)) {
            cache.set(item, expensiveCalculation(item));
        }
        return cache.get(item) > threshold;
    });
}

// ---- WEB WORKERS FOR HEAVY TASKS ----
// worker.js
self.onmessage = function(e) {
    const result = heavyComputation(e.data);
    self.postMessage(result);
};

function heavyComputation(data) {
    // Long-running calculation
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
        result += Math.sqrt(i);
    }
    return result;
}

// main.js
function runHeavyTask(data) {
    const worker = new Worker('worker.js');
    return new Promise((resolve) => {
        worker.onmessage = (e) => {
            resolve(e.data);
            worker.terminate();
        };
        worker.postMessage(data);
    });
}

// ---- BATCH UPDATES WITH REQUESTIDLECALLBACK ----
function batchUpdates(items) {
    let index = 0;
    
    function processBatch(deadline) {
        while (index < items.length && deadline.timeRemaining() > 0) {
            // Process one item
            processItem(items[index]);
            index++;
        }
        
        if (index < items.length) {
            requestIdleCallback(processBatch);
        }
    }
    
    requestIdleCallback(processBatch);
}

function processItem(item) {
    // Process individual item
    console.log('Processing:', item);
}

Performance optimization reduces execution time and memory usage. Batch DOM updates minimize reflows. Caching avoids repeated calculations. Lazy loading defers resource loading. Web Workers handle heavy tasks in background threads. requestAnimationFrame synchronizes animations with display refresh.

TypeScript & Lightweight Libraries

8.1 Static Typing

TypeScript extends JavaScript with optional static typing, allowing many type-related errors to be detected during compilation before the code runs.

  • Static types are checked during compilation, allowing many type-related errors to be detected before the program is executed.
  • Type annotations – : type syntax
  • Type inference – Types inferred when not specified
  • Type safety – Prevents type errors
  • Tooling – Better IDE support

Code Example:

// Basic types
let username: string = "Alice";
let age: number = 25;
let isActive: boolean = true;
let scores: number[] = [10, 20, 30];
let user: { name: string; age: number } = { name: "Bob", age: 30 };

// Type inference
let inferredName = "Sparrow"; // inferred as string
// inferredName = 10; // Error: Type 'number' not assignable to type 'string'

// Union types
let id: number | string = 10;
id = "ABC"; // OK
// id = true; // Error: Type 'boolean' not assignable to type 'number | string'

// Optional types
function greet(name?: string): string {
    return `Hello, ${name ?? "Guest"}!`;
}
console.log(greet()); // Hello, Guest!
console.log(greet("Alice")); // Hello, Alice!

// Type aliases
type Bird = {
    species: string;
    count: number;
    weight: number;
};

const eagle: Bird = {
    species: "Eagle",
    count: 3,
    weight: 4500
};

// Interface
interface BirdInterface {
    species: string;
    count: number;
    weight: number;
    isMigratory?: boolean;
}

const sparrow: BirdInterface = {
    species: "Sparrow",
    count: 10,
    weight: 25.5,
    isMigratory: true
};

// Function types
type Calculator = (a: number, b: number) => number;
const add: Calculator = (a, b) => a + b;
const subtract: Calculator = (a, b) => a - b;

console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2

TypeScript types are checked at compile time. Type annotations specify types. Type inference determines types from values. Union types allow multiple types. Optional properties use ?. Interfaces define object shapes.

8.2 Interfaces

Interfaces define object shapes and contracts.

  • Shape definition – Define object structure
  • Implementation – Classes can implement interfaces
  • Extension – Interfaces can extend other interfaces
  • Optional properties – ? for optional properties
  • Readonly properties – readonly for immutable properties

Code Example:

// Basic interface
interface Bird {
    species: string;
    count: number;
    weight: number;
}

// Using interface
const eagle: Bird = {
    species: "Eagle",
    count: 3,
    weight: 4500
};

// Optional properties
interface BirdWithHabitat extends Bird {
    habitat?: string;
    isMigratory?: boolean;
}

const sparrow: BirdWithHabitat = {
    species: "Sparrow",
    count: 10,
    weight: 25.5,
    isMigratory: true
};

// Readonly properties
interface BirdReadonly {
    readonly id: number;
    species: string;
    count: number;
}

const hawk: BirdReadonly = {
    id: 1,
    species: "Hawk",
    count: 5
};
// hawk.id = 2; // Error: Cannot assign to 'id' because it is a read-only property

// Function interface
interface BirdProcessor {
    (birds: Bird[]): Bird[];
}

const filterHeavyBirds: BirdProcessor = (birds) => {
    return birds.filter(bird => bird.weight > 1000);
};

// Index signature
interface BirdDictionary {
    [species: string]: number;
}

const birdCounts: BirdDictionary = {
    Eagle: 3,
    Sparrow: 10,
    Hawk: 5
};

// Extending interfaces
interface FlyingBird extends Bird {
    wingspan: number;
    canFly: boolean;
}

const eagleFlying: FlyingBird = {
    species: "Eagle",
    count: 3,
    weight: 4500,
    wingspan: 2.3,
    canFly: true
};

// Implementing interface in class
class BirdClass implements Bird {
    constructor(
        public species: string,
        public count: number,
        public weight: number
    ) {}
    
    speak(): void {
        console.log(`${this.species} chirps`);
    }
}

const myBird = new BirdClass("Sparrow", 10, 25.5);
myBird.speak(); // Sparrow chirps

Interfaces define object shapes. They can be extended. Optional properties use ?. Readonly properties prevent modification. Interfaces can describe functions. Classes implement interfaces.

8.3 Enums

Enums define a set of named constants.

  • Numeric enums – Default numeric values
  • String enums – String values
  • Heterogeneous enums – Mixed types
  • Const enums – Inlined at compile time
  • Reverse mapping – Key-value both directions

Code Example:

// Numeric enum (auto-incrementing)
enum BirdStatus {
    Active,
    Migrating,
    Nesting,
    Endangered
}

console.log(BirdStatus.Active); // 0
console.log(BirdStatus.Migrating); // 1

// Numeric enum with custom values
enum BirdSize {
    Small = 1,
    Medium = 2,
    Large = 3
}

console.log(BirdSize.Small); // 1
console.log(BirdSize.Large); // 3

// String enum
enum BirdColor {
    Brown = "brown",
    Red = "red",
    Blue = "blue",
    Black = "black"
}

console.log(BirdColor.Brown); // "brown"

// Heterogeneous enum
enum BirdMix {
    Eagle = 1,
    Sparrow = "Sparrow",
    Hawk = 2
}

console.log(BirdMix.Eagle); // 1
console.log(BirdMix.Sparrow); // "Sparrow"

// Const enum (inlined at compile time)
const enum BirdType {
    Predator,
    Songbird,
    Waterbird
}

console.log(BirdType.Predator); // Inlined to 0

// Using enums in functions
function getBirdStatus(status: BirdStatus): string {
    switch (status) {
        case BirdStatus.Active:
            return "Bird is active";
        case BirdStatus.Migrating:
            return "Bird is migrating";
        case BirdStatus.Nesting:
            return "Bird is nesting";
        case BirdStatus.Endangered:
            return "Bird is endangered";
        default:
            return "Unknown status";
    }
}

console.log(getBirdStatus(BirdStatus.Migrating)); // "Bird is migrating"

// Reverse mapping (numeric enums)
enum BirdLegs {
    Two = 2,
    Four = 4
}

const legCount: number = 2;
const legEnum = BirdLegs[legCount];
console.log(legEnum); // "Two"

// Object with enum keys
type BirdData = {
    [key in BirdSize]: number;
};

const data: BirdData = {
    [BirdSize.Small]: 10,
    [BirdSize.Medium]: 5,
    [BirdSize.Large]: 2
};

console.log(data); // { 1: 10, 2: 5, 3: 2 }

Enums define named constants. Numeric enums auto-increment. String enums use string values. Const enums are inlined for performance. Reverse mapping works for numeric enums.

8.4 Generics

Generics allow developers to create reusable components that can work with different data types while maintaining type safety.

  • Type parameters – <T> syntax
  • Generic functions – Functions that work with any type
  • Generic classes – Classes with type parameters
  • Constraints – Restrict type parameters
  • Default types – Default type parameters

Code Example:

// Generic function
function identity<T>(value: T): T {
    return value;
}

console.log(identity<string>("Hello")); // Hello
console.log(identity<number>(42)); // 42
console.log(identity("Inferred")); // Type inferred as string

// Generic function with multiple types
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

console.log(pair<string, number>("Eagle", 3)); // ["Eagle", 3]

// Generic class
class BirdCage<T> {
    private bird: T | null = null;
    
    putBird(bird: T): void {
        this.bird = bird;
        console.log(`Bird placed: ${JSON.stringify(bird)}`);
    }
    
    getBird(): T | null {
        return this.bird;
    }
    
    isEmpty(): boolean {
        return this.bird === null;
    }
}

const stringCage = new BirdCage<string>();
stringCage.putBird("Sparrow");
console.log(stringCage.getBird()); // "Sparrow"

const objectCage = new BirdCage<{ species: string; count: number }>();
objectCage.putBird({ species: "Eagle", count: 3 });
console.log(objectCage.getBird()); // { species: "Eagle", count: 3 }

// Generic interface
interface Repository<T> {
    get(id: number): T;
    save(item: T): void;
    delete(id: number): void;
}

class BirdRepository implements Repository<{ species: string; count: number }> {
    private data: Map<number, { species: string; count: number }> = new Map();
    private nextId = 1;
    
    get(id: number): { species: string; count: number } {
        return this.data.get(id);
    }
    
    save(item: { species: string; count: number }): void {
        this.data.set(this.nextId++, item);
    }
    
    delete(id: number): void {
        this.data.delete(id);
    }
}

// Generic with constraints
function getLength<T extends { length: number }>(item: T): number {
    return item.length;
}

console.log(getLength("Sparrow")); // 7
console.log(getLength([1, 2, 3])); // 3
// console.log(getLength(42)); // Error: number doesn't have length

// Generic with default type
class DefaultCage<T = string> {
    private data: T[] = [];
    
    add(item: T): void {
        this.data.push(item);
    }
    
    getAll(): T[] {
        return this.data;
    }
}

const defaultCage = new DefaultCage(); // T is string
defaultCage.add("Sparrow");
defaultCage.add("Eagle");
console.log(defaultCage.getAll()); // ["Sparrow", "Eagle"]

Generics use type parameters <T>. They enable type-safe code reuse. Type constraints restrict allowed types. Default types provide fallbacks. Generics work with functions, classes, and interfaces.

8.5 Type Guards

Type guards narrow down the type of a variable within a block.

  • typeof – Primitive type guard
  • instanceof – Class type guard
  • in – Property existence guard
  • is – Custom type guard (type predicate)
  • as – Type assertion

Code Example:

// ---- TYPEOF TYPE GUARD ----
function processValue(value: string | number | boolean) {
    if (typeof value === "string") {
        // value is string here
        console.log(`String length: ${value.length}`);
    } else if (typeof value === "number") {
        // value is number here
        console.log(`Number square: ${value ** 2}`);
    } else {
        // value is boolean here
        console.log(`Boolean value: ${value}`);
    }
}

processValue("Hello"); // String length: 5
processValue(42); // Number square: 1764
processValue(true); // Boolean value: true

// ---- INSTANCEOF TYPE GUARD ----
class Bird {
    species: string;
    constructor(species: string) {
        this.species = species;
    }
    chirp(): void {
        console.log(`${this.species} chirps`);
    }
}

class Eagle extends Bird {
    hunt(): void {
        console.log(`${this.species} hunts`);
    }
}

function processBird(bird: Bird) {
    if (bird instanceof Eagle) {
        // bird is Eagle here
        bird.hunt();
    } else {
        // bird is Bird here
        bird.chirp();
    }
}

const eagle = new Eagle("Eagle");
processBird(eagle); // Eagle hunts

// ---- IN TYPE GUARD ----
interface BirdData {
    species: string;
    count: number;
}

interface BirdWithWeight extends BirdData {
    weight: number;
}

function processBirdData(data: BirdData | BirdWithWeight) {
    if ("weight" in data) {
        // data is BirdWithWeight here
        console.log(`${data.species}: ${data.weight}g`);
    } else {
        // data is BirdData here
        console.log(`${data.species}: ${data.count} birds`);
    }
}

processBirdData({ species: "Eagle", count: 3 });
processBirdData({ species: "Sparrow", count: 10, weight: 25.5 });

// ---- CUSTOM TYPE GUARD (type predicate) ----
function isBirdWithWeight(data: BirdData): data is BirdWithWeight {
    return (data as BirdWithWeight).weight !== undefined;
}

function processBirdData2(data: BirdData | BirdWithWeight) {
    if (isBirdWithWeight(data)) {
        // data is BirdWithWeight here
        console.log(`${data.species}: ${data.weight}g`);
    } else {
        console.log(`${data.species}: ${data.count} birds`);
    }
}

// ---- TYPE ASSERTION (as) ----
interface BirdInfo {
    species: string;
    count: number;
}

const data = JSON.parse('{"species":"Eagle","count":3}') as BirdInfo;
console.log(data.species); // Eagle

// ---- DISCRIMINATED UNION ----
interface BirdWithSpecies {
    type: "species";
    name: string;
}

interface BirdWithCount {
    type: "count";
    value: number;
}

type BirdUnion = BirdWithSpecies | BirdWithCount;

function processUnion(value: BirdUnion) {
    if (value.type === "species") {
        console.log(`Bird species: ${value.name}`);
    } else {
        console.log(`Bird count: ${value.value}`);
    }
}

processUnion({ type: "species", name: "Eagle" });
processUnion({ type: "count", value: 3 });

Type guards narrow types in conditional blocks. typeof works with primitives. instanceof checks class inheritance. in checks property existence. Custom guards use type predicates (is). Discriminated unions use a common property.

8.6 Type Inference

TypeScript infers types when not explicitly annotated.

  • Variable inference – Types from initialization
  • Return inference – Types from return statements
  • Contextual typing – Types from context
  • Best common type – Least specific type
  • Literal inference – String literal types

Code Example:

// ---- VARIABLE INFERENCE ----
let species = "Sparrow"; // inferred as string
// species = 10; // Error: Type 'number' not assignable to string

let count = 10; // inferred as number
// count = "ten"; // Error: Type 'string' not assignable to number

// ---- ARRAY INFERENCE ----
const birds = ["Eagle", "Sparrow", "Hawk"]; // inferred as string[]

const numbers = [1, 2, 3]; // inferred as number[]

// ---- OBJECT INFERENCE ----
const bird = {
    species: "Eagle",
    count: 3,
    weight: 4500
}; // inferred as { species: string; count: number; weight: number; }

// bird.species = 10; // Error: species must be string

// ---- RETURN INFERENCE ----
function add(a: number, b: number) {
    return a + b; // inferred return type: number
}

function getBirdInfo(species: string, count: number) {
    return { species, count }; // inferred return type: { species: string; count: number; }
}

// ---- CONTEXTUAL TYPING ----
const birds2: string[] = ["Eagle", "Sparrow", "Hawk"];
birds2.forEach((bird) => {
    // bird is inferred as string from context
    console.log(bird.toUpperCase());
});

// ---- BEST COMMON TYPE ----
const mixed = [1, "two", 3]; // inferred as (string | number)[]

// ---- LITERAL INFERENCE ----
const literalSpecies = "Eagle"; // inferred as string (widened)
const literalCount = 42; // inferred as number (widened)

// Const assertion (preserve literal types)
const asConst = {
    species: "Eagle" as const,
    count: 42 as const
}; // asConst.species is type "Eagle", asConst.count is type 42

// ---- INFERENCE IN GENERICS ----
function identity<T>(value: T): T {
    return value;
}

const result1 = identity("Hello"); // inferred as string
const result2 = identity(42); // inferred as number
const result3 = identity({ species: "Eagle" }); // inferred as { species: string; }

// ---- TYPE WIDENING ----
let x = 10; // inferred as number (widened from literal)
const y = 10; // inferred as 10 (literal type)

// ---- NO IMPLICIT ANY ----
function process(value: unknown) {
    // value is unknown, not any
    // if (typeof value === "string") { ... }
}

TypeScript infers types from values. Variable types come from initialization. Return types come from return statements. Context provides type information. Arrays infer union types. Literals can be preserved with as const.

8.7 TS with React

Typing React components with TypeScript.

  • React.FC – Function component type
  • Props interface – Define component props
  • State typing – Type useState hook
  • Refs – Typed refs
  • Events – Typed event handlers

Code Example:

// ---- BASIC COMPONENT WITH PROPS ----
import React, { useState, useRef, FormEvent } from 'react';

// Props interface
interface BirdCardProps {
    species: string;
    count: number;
    weight?: number;
    onUpdate?: (count: number) => void;
}

// Function component with React.FC
const BirdCard: React.FC<BirdCardProps> = ({ species, count, weight, onUpdate }) => {
    const [birdCount, setBirdCount] = useState<number>(count);
    const inputRef = useRef<HTMLInputElement>(null);
    
    const handleUpdate = () => {
        setBirdCount(birdCount + 1);
        onUpdate?.(birdCount + 1);
    };
    
    return (
        <div className="bird-card">
            <h2>{species}</h2>
            <p>Count: {birdCount}</p>
            {weight && <p>Weight: {weight}g</p>}
            <button onClick={handleUpdate}>Add Bird</button>
        </div>
    );
};

// ---- COMPONENT WITH CHILDREN ----
interface BirdListProps {
    title: string;
    children: React.ReactNode;
    onAdd?: (species: string) => void;
}

const BirdList: React.FC<BirdListProps> = ({ title, children, onAdd }) => {
    const [inputValue, setInputValue] = useState<string>("");
    
    const handleSubmit = (e: FormEvent) => {
        e.preventDefault();
        if (inputValue.trim()) {
            onAdd?.(inputValue);
            setInputValue("");
        }
    };
    
    return (
        <div>
            <h3>{title}</h3>
            <form onSubmit={handleSubmit}>
                <input
                    ref={inputRef}
                    type="text"
                    value={inputValue}
                    onChange={(e) => setInputValue(e.target.value)}
                    placeholder="Add bird species"
                />
                <button type="submit">Add</button>
            </form>
            <ul>{children}</ul>
        </div>
    );
};

// ---- CUSTOM HOOK WITH TYPES ----
interface Bird {
    id: number;
    species: string;
    count: number;
}

function useBirds(initialBirds: Bird[] = []) {
    const [birds, setBirds] = useState<Bird[]>(initialBirds);
    const [nextId, setNextId] = useState<number>(initialBirds.length + 1);
    
    const addBird = (species: string, count: number): void => {
        const newBird: Bird = { id: nextId, species, count };
        setBirds([...birds, newBird]);
        setNextId(nextId + 1);
    };
    
    const removeBird = (id: number): void => {
        setBirds(birds.filter(bird => bird.id !== id));
    };
    
    const updateBird = (id: number, count: number): void => {
        setBirds(birds.map(bird => 
            bird.id === id ? { ...bird, count } : bird
        ));
    };
    
    return { birds, addBird, removeBird, updateBird };
}

// ---- USING HOOK IN COMPONENT ----
const BirdTracker: React.FC = () => {
    const { birds, addBird, removeBird, updateBird } = useBirds([
        { id: 1, species: "Eagle", count: 3 },
        { id: 2, species: "Sparrow", count: 10 }
    ]);
    
    return (
        <div>
            <h1>Bird Tracker</h1>
            {birds.map(bird => (
                <BirdCard
                    key={bird.id}
                    species={bird.species}
                    count={bird.count}
                    onUpdate={(count) => updateBird(bird.id, count)}
                />
            ))}
        </div>
    );
};

// ---- EVENT HANDLER TYPES ----
interface BirdFormProps {
    onSubmit: (data: { species: string; count: number }) => void;
}

const BirdForm: React.FC<BirdFormProps> = ({ onSubmit }) => {
    const [species, setSpecies] = useState<string>("");
    const [count, setCount] = useState<number>(0);
    
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        onSubmit({ species, count });
        setSpecies("");
        setCount(0);
    };
    
    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                value={species}
                onChange={(e: React.ChangeEvent<HTMLInputElement>) => 
                    setSpecies(e.target.value)
                }
                placeholder="Species"
            />
            <input
                type="number"
                value={count}
                onChange={(e: React.ChangeEvent<HTMLInputElement>) => 
                    setCount(parseInt(e.target.value) || 0)
                }
                placeholder="Count"
            />
            <button type="submit">Add Bird</button>
        </form>
    );
};

React components can use TypeScript interfaces to define the structure and types of their props, helping ensure that components receive the expected data. useState is typed with generics. useRef types DOM elements. Events use React’s event types. Custom hooks return typed values. Function components use React.FC or explicit return types.

8.8 Lightweight Libraries (Stimulus, Alpine.js, HTMX)

Lightweight libraries for enhancing HTML with minimal JavaScript.

Stimulus:

  • Controllers for HTML
  • Small footprint
  • Progressive enhancement
  • No build step required

Alpine.js:

  • Reactive, minimal framework
  • Vue-like syntax
  • No build step required
  • Good for small to medium projects

HTMX:

  • AJAX with HTML attributes
  • CSS transitions
  • No build step required
  • Server-driven UI

Code Example:

<!-- ---- ALPINE.JS ---- -->
<!-- Include Alpine.js -->
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

<!-- Alpine.js example -->
<div x-data="{ 
    birds: ['Eagle', 'Sparrow', 'Hawk'], 
    newBird: '',
    addBird() {
        if (this.newBird.trim()) {
            this.birds.push(this.newBird);
            this.newBird = '';
        }
    },
    removeBird(index) {
        this.birds.splice(index, 1);
    }
}">
    <h2>Bird List</h2>
    
    <ul>
        <template x-for="(bird, index) in birds" :key="index">
            <li x-text="bird" @click="removeBird(index)"></li>
        </template>
    </ul>
    
    <div>
        <input type="text" x-model="newBird" placeholder="Add bird">
        <button @click="addBird">Add Bird</button>
    </div>
    
    <p x-text="'Total birds: ' + birds.length"></p>
</div>

<!-- ---- STIMULUS ---- -->
<!-- Include Stimulus -->
<script src="https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.x.x/dist/stimulus.min.js"></script>

<!-- Stimulus controller -->
<script>
// bird_controller.js
class BirdController extends Stimulus.Controller {
    static targets = ['list', 'input'];
    
    connect() {
        this.birds = ['Eagle', 'Sparrow', 'Hawk'];
        this.render();
    }
    
    add() {
        const input = this.inputTarget;
        if (input.value.trim()) {
            this.birds.push(input.value);
            input.value = '';
            this.render();
        }
    }
    
    remove(index) {
        this.birds.splice(index, 1);
        this.render();
    }
    
    render() {
        this.listTarget.innerHTML = this.birds.map((bird, i) => 
            `<li data-action="click->bird#remove:${i}">${bird}</li>`
        ).join('');
    }
}
</script>

<!-- HTML -->
<div data-controller="bird">
    <h2>Bird List</h2>
    <ul data-bird-target="list"></ul>
    <input type="text" data-bird-target="input" placeholder="Add bird">
    <button data-action="click->bird#add">Add Bird</button>
</div>

<!-- ---- HTMX ---- -->
<!-- Include HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.2"></script>

<!-- HTMX example -->
<div>
    <h2>Bird Search</h2>
    
    <!-- Search with HTMX -->
    <input type="search"
           name="search"
           hx-get="/search-birds"
           hx-trigger="keyup changed delay:500ms"
           hx-target="#bird-results"
           hx-indicator="#loading"
           placeholder="Search birds...">
    
    <!-- Loading indicator -->
    <div id="loading" class="htmx-indicator">Searching...</div>
    
    <!-- Results container -->
    <div id="bird-results">
        <!-- Results loaded here -->
    </div>
    
    <!-- HTMX form -->
    <form hx-post="/add-bird" hx-target="#bird-list">
        <input type="text" name="species" placeholder="Species">
        <input type="number" name="count" placeholder="Count">
        <button type="submit">Add Bird</button>
    </form>
    
    <div id="bird-list">
        <!-- Bird list here -->
    </div>
</div>

Alpine.js adds reactivity to HTML with x-data, x-model, x-for, and @click attributes. Stimulus uses controllers to add behavior to HTML. HTMX enables AJAX with HTML attributes. All three are lightweight with no build step required.

Frontend Libraries

9.1 React

React is a library for building user interfaces with components, JSX, props, state, and hooks.

  • Components – Reusable UI pieces
  • JSX – JavaScript XML syntax
  • Props – Input data to components
  • State – Component data
  • Hooks – React’s functional API
  • Virtual DOM – Efficient updates

Code Example:

import React, { useState, useEffect } from 'react';

// ---- FUNCTIONAL COMPONENT ----
function BirdCard({ species, count, onUpdate }) {
    const [localCount, setLocalCount] = useState(count);
    const [isExpanded, setIsExpanded] = useState(false);
    
    useEffect(() => {
        // Side effect when count changes
        console.log(`Bird ${species} count updated to ${localCount}`);
    }, [localCount, species]);
    
    const handleIncrement = () => {
        const newCount = localCount + 1;
        setLocalCount(newCount);
        onUpdate?.(newCount);
    };
    
    return (
        <div className="bird-card">
            <h3 onClick={() => setIsExpanded(!isExpanded)}>
                {species} {isExpanded ? '▼' : '▶'}
            </h3>
            {isExpanded && (
                <>
                    <p>Count: {localCount}</p>
                    <button onClick={handleIncrement}>Add Bird</button>
                </>
            )}
        </div>
    );
}

// ---- COMPONENT WITH STATE AND EFFECTS ----
function BirdList() {
    const [birds, setBirds] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    // Fetch data on mount
    useEffect(() => {
        fetch('/api/birds')
            .then(res => {
                if (!res.ok) throw new Error('Failed to fetch');
                return res.json();
            })
            .then(data => {
                setBirds(data);
                setLoading(false);
            })
            .catch(err => {
                setError(err.message);
                setLoading(false);
            });
    }, []);
    
    if (loading) return <div>Loading birds...</div>;
    if (error) return <div>Error: {error}</div>;
    
    return (
        <div className="bird-list">
            {birds.map(bird => (
                <BirdCard
                    key={bird.id}
                    species={bird.species}
                    count={bird.count}
                    onUpdate={(newCount) => {
                        // Update birds state
                        setBirds(prev => prev.map(b => 
                            b.id === bird.id ? { ...b, count: newCount } : b
                        ));
                    }}
                />
            ))}
        </div>
    );
}

// ---- CUSTOM HOOK ----
function useLocalStorage(key, initialValue) {
    const [storedValue, setStoredValue] = useState(() => {
        try {
            const item = localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
        } catch (error) {
            console.error(error);
            return initialValue;
        }
    });
    
    const setValue = (value) => {
        try {
            const valueToStore = value instanceof Function ? value(storedValue) : value;
            setStoredValue(valueToStore);
            localStorage.setItem(key, JSON.stringify(valueToStore));
        } catch (error) {
            console.error(error);
        }
    };
    
    return [storedValue, setValue];
}

// ---- COMPONENT WITH CUSTOM HOOK ----
function BirdPreferences() {
    const [preferences, setPreferences] = useLocalStorage('birdPreferences', {
        showWeight: true,
        sortBy: 'name'
    });
    
    const toggleWeight = () => {
        setPreferences(prev => ({ ...prev, showWeight: !prev.showWeight }));
    };
    
    return (
        <div>
            <h2>Bird Preferences</h2>
            <button onClick={toggleWeight}>
                {preferences.showWeight ? 'Hide' : 'Show'} Weight
            </button>
            <p>Current sort: {preferences.sortBy}</p>
        </div>
    );
}

// ---- CONTEXT API ----
const BirdContext = React.createContext();

function BirdProvider({ children }) {
    const [birds, setBirds] = useState([]);
    const [user, setUser] = useState(null);
    
    const value = {
        birds,
        setBirds,
        user,
        setUser,
        addBird: (bird) => setBirds(prev => [...prev, bird]),
        removeBird: (id) => setBirds(prev => prev.filter(b => b.id !== id))
    };
    
    return (
        <BirdContext.Provider value={value}>
            {children}
        </BirdContext.Provider>
    );
}

// ---- USING CONTEXT ----
function BirdConsumer() {
    const { birds, addBird } = React.useContext(BirdContext);
    
    const handleAdd = () => {
        addBird({ id: Date.now(), species: "New Bird", count: 1 });
    };
    
    return (
        <div>
            <h3>Bird Count: {birds.length}</h3>
            <button onClick={handleAdd}>Add Bird</button>
        </div>
    );
}

React components are functions returning JSX. State is managed with useState. Effects run with useEffect. Custom hooks reuse logic. Context provides global state. React components render their UI based on their current props and state, updating the output when those values change.

9.2 Vue.js

Vue is a progressive framework with reactive data and single-file components.

  • Reactive data – data() function
  • Templates – HTML templates
  • Directives – v-if, v-for, v-model
  • Components – Reusable Vue components
  • Composition API – setup() function
  • Reactive refs – ref() and reactive()

Code Example:

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
</head>
<body>
    <!-- ---- OPTIONS API ---- -->
    <div id="app">
        <h2>{{ title }}</h2>
        
        <div>
            <input type="text" v-model="newBird" placeholder="Add bird">
            <button @click="addBird">Add Bird</button>
        </div>
        
        <ul>
            <li v-for="(bird, index) in birds" :key="bird.id">
                {{ bird.species }} ({{ bird.count }})
                <button @click="removeBird(index)">Remove</button>
            </li>
        </ul>
        
        <p>Total birds: {{ totalBirds }}</p>
    </div>

    <script>
        new Vue({
            el: '#app',
            data: {
                title: 'Bird Tracker',
                birds: [
                    { id: 1, species: 'Eagle', count: 3 },
                    { id: 2, species: 'Sparrow', count: 10 },
                    { id: 3, species: 'Hawk', count: 5 }
                ],
                newBird: ''
            },
            computed: {
                totalBirds() {
                    return this.birds.reduce((sum, b) => sum + b.count, 0);
                }
            },
            methods: {
                addBird() {
                    if (this.newBird.trim()) {
                        this.birds.push({
                            id: Date.now(),
                            species: this.newBird,
                            count: 0
                        });
                        this.newBird = '';
                    }
                },
                removeBird(index) {
                    this.birds.splice(index, 1);
                }
            }
        });
    </script>

    <!-- ---- COMPOSITION API (Vue 3) ---- -->
    <script setup>
        import { ref, computed, onMounted } from 'vue';
        
        const birds = ref([
            { id: 1, species: 'Eagle', count: 3 },
            { id: 2, species: 'Sparrow', count: 10 }
        ]);
        const newBird = ref('');
        
        const totalBirds = computed(() => {
            return birds.value.reduce((sum, b) => sum + b.count, 0);
        });
        
        const addBird = () => {
            if (newBird.value.trim()) {
                birds.value.push({
                    id: Date.now(),
                    species: newBird.value,
                    count: 0
                });
                newBird.value = '';
            }
        };
        
        const removeBird = (index) => {
            birds.value.splice(index, 1);
        };
        
        onMounted(() => {
            console.log('Bird tracker mounted');
        });
    </script>
</body>
</html>

<!-- ---- SINGLE-FILE COMPONENT (.vue) ---- -->
<template>
    <div class="bird-counter">
        <h2>{{ title }}</h2>
        <p>Count: {{ count }}</p>
        <button @click="increment">+1</button>
        <button @click="decrement">-1</button>
    </div>
</template>

<script>
    export default {
        name: 'BirdCounter',
        props: {
            title: {
                type: String,
                default: 'Bird Counter'
            },
            initialCount: {
                type: Number,
                default: 0
            }
        },
        data() {
            return {
                count: this.initialCount
            };
        },
        methods: {
            increment() {
                this.count++;
                this.$emit('update', this.count);
            },
            decrement() {
                this.count--;
                this.$emit('update', this.count);
            }
        },
        watch: {
            count(newVal, oldVal) {
                console.log(`Count changed: ${oldVal} -> ${newVal}`);
            }
        }
    };
</script>

<style scoped>
    .bird-counter {
        padding: 20px;
        border: 1px solid #ccc;
        border-radius: 8px;
    }
    button {
        margin: 0 4px;
        padding: 4px 12px;
    }
</style>

Vue uses reactive data in data(). v-model binds form inputs. v-for loops over arrays. v-if conditionally renders. computed properties are cached. Methods handle events. The Composition API uses ref() and reactive() for reactive state.

9.3 jQuery

jQuery simplifies DOM manipulation and AJAX (modern use is diminishing).

  • DOM traversal – $() selector
  • DOM manipulation – html(), text(), append()
  • Event handling – on(), click()
  • AJAX – $.ajax(), $.get(), $.post()
  • Effects – fadeIn(), slideUp()
  • Animations – animate()

Code Example:

<!DOCTYPE html>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
</head>
<body>
    <div id="app">
        <h2>Bird Tracker</h2>
        
        <div>
            <input type="text" id="birdInput" placeholder="Add bird">
            <button id="addBtn">Add Bird</button>
        </div>
        
        <ul id="birdList">
            <li>Eagle (3)</li>
            <li>Sparrow (10)</li>
            <li>Hawk (5)</li>
        </ul>
        
        <div id="birdCount">Total: 18 birds</div>
        
        <button id="fetchBtn">Fetch Bird Data</button>
        <div id="birdData"></div>
        
        <button id="animateBtn">Animate</button>
        <div id="animatedBox" style="width:100px;height:100px;background:blue;"></div>
    </div>

    <script>
        $(document).ready(function() {
            // ---- DOM MANIPULATION ----
            $('#addBtn').on('click', function() {
                const bird = $('#birdInput').val().trim();
                if (bird) {
                    $('#birdList').append(`<li>${bird} (0)</li>`);
                    updateCount();
                    $('#birdInput').val('');
                }
            });
            
            // ---- EVENT DELEGATION ----
            $('#birdList').on('click', 'li', function() {
                $(this).toggleClass('selected');
            });
            
            // ---- AJAX ----
            $('#fetchBtn').on('click', function() {
                $.ajax({
                    url: '/api/birds',
                    method: 'GET',
                    dataType: 'json',
                    beforeSend: function() {
                        $('#birdData').text('Loading...');
                    },
                    success: function(data) {
                        let html = '<ul>';
                        data.forEach(bird => {
                            html += `<li>${bird.species}: ${bird.count}</li>`;
                        });
                        html += '</ul>';
                        $('#birdData').html(html);
                    },
                    error: function(err) {
                        $('#birdData').text('Error: ' + err.statusText);
                    }
                });
            });
            
            // ---- EFFECTS ----
            $('#animateBtn').on('click', function() {
                $('#animatedBox').animate({
                    width: '200px',
                    height: '200px',
                    opacity: 0.5
                }, 1000);
            });
            
            // ---- UTILITY FUNCTIONS ----
            function updateCount() {
                const items = $('#birdList li');
                const total = items.length;
                $('#birdCount').text(`Total: ${total} birds`);
            }
            
            // ---- CHAINING ----
            $('.highlight')
                .addClass('active')
                .fadeIn(500)
                .css('color', 'red');
            
            // ---- GETTER/SETTER ----
            const titleText = $('h2').text(); // getter
            $('h2').text('New Bird Tracker'); // setter
        });
    </script>
</body>
</html>

jQuery uses $() to select elements. Methods chain for concise code. Event handlers use on(). AJAX uses $.ajax() for requests. Effects animate elements. DOM manipulation methods modify content.

Backend Development

11.1 Node.js

Node.js is a JavaScript runtime for building server-side applications.

  • Event-driven – Non-blocking I/O
  • Single-threaded – Uses event loop
  • NPM – Package manager
  • Modules – Built-in and third-party
  • HTTP server – Handle requests

Code Example:

// ---- BASIC HTTP SERVER ----
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
    console.log(`${req.method} ${req.url}`);
    
    // Routing
    if (req.url === '/' && req.method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(`
            <h1>Bird API</h1>
            <p>Endpoints:</p>
            <ul>
                <li>GET /birds - List all birds</li>
                <li>POST /birds - Add a bird</li>
                <li>GET /birds/:id - Get a bird</li>
            </ul>
        `);
    } else if (req.url === '/birds' && req.method === 'GET') {
        // GET all birds
        const birds = [
            { id: 1, species: 'Eagle', count: 3 },
            { id: 2, species: 'Sparrow', count: 10 }
        ];
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(birds));
    } else if (req.url === '/birds' && req.method === 'POST') {
        // POST new bird
        let body = '';
        req.on('data', chunk => { body += chunk; });
        req.on('end', () => {
            try {
                const bird = JSON.parse(body);
                bird.id = Date.now();
                res.writeHead(201, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify(bird));
            } catch (e) {
                res.writeHead(400, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: 'Invalid JSON' }));
            }
        });
    } else if (req.url.startsWith('/birds/') && req.method === 'GET') {
        // GET bird by ID
        const id = parseInt(req.url.split('/')[2]);
        const bird = { id, species: 'Eagle', count: 3 };
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(bird));
    } else {
        // 404
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
    }
});

// Start server
const PORT = 3000;
server.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

// ---- FILE SYSTEM OPERATIONS ----
function readBirdsFile() {
    const filePath = path.join(__dirname, 'birds.json');
    try {
        const data = fs.readFileSync(filePath, 'utf8');
        return JSON.parse(data);
    } catch (error) {
        return [];
    }
}

function writeBirdsFile(birds) {
    const filePath = path.join(__dirname, 'birds.json');
    fs.writeFileSync(filePath, JSON.stringify(birds, null, 2));
}

// ---- STREAMS ----
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('copy.txt');
readStream.pipe(writeStream);

// ---- EVENTS ----
const EventEmitter = require('events');
class BirdTracker extends EventEmitter {
    addBird(bird) {
        this.emit('birdAdded', bird);
        return bird;
    }
}

const tracker = new BirdTracker();
tracker.on('birdAdded', (bird) => {
    console.log(`Bird added: ${bird.species}`);
});
tracker.addBird({ species: 'Eagle', count: 3 });

Node.js uses the event loop for non-blocking operations. The HTTP server handles requests asynchronously. File operations are available via the fs module. Events enable custom communication. Streams handle large data efficiently.

11.2 Express.js

Express is a minimal web framework for Node.js.

  • Routing – HTTP methods and paths
  • Middleware – Functions that process requests
  • Error handling – Centralized error handling
  • Static files – Serve static assets
  • Templates – View engines
  • Request/Response – Enhanced objects

Code Example:

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');

// ---- CREATE APP ----
const app = express();
const PORT = 3000;

// ---- MIDDLEWARE ----
app.use(helmet()); // Security headers
app.use(cors()); // CORS support
app.use(express.json()); // JSON body parsing
app.use(express.urlencoded({ extended: true })); // Form data
app.use(express.static('public')); // Static files

// ---- DATA ----
let birds = [
    { id: 1, species: 'Eagle', count: 3, weight: 4500 },
    { id: 2, species: 'Sparrow', count: 10, weight: 25.5 },
    { id: 3, species: 'Hawk', count: 5, weight: 1200 }
];
let nextId = 4;

// ---- ROUTES ----

// GET all birds
app.get('/api/birds', (req, res) => {
    // Query parameters
    const { limit = 10, sort = 'id', order = 'asc' } = req.query;
    
    let result = [...birds];
    result = result.slice(0, parseInt(limit));
    res.json({
        success: true,
        data: result,
        total: birds.length,
        query: req.query
    });
});

// GET bird by ID
app.get('/api/birds/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const bird = birds.find(b => b.id === id);
    
    if (!bird) {
        return res.status(404).json({
            success: false,
            error: 'Bird not found'
        });
    }
    
    res.json({ success: true, data: bird });
});

// POST new bird
app.post('/api/birds', (req, res) => {
    const { species, count, weight } = req.body;
    
    // Validation
    if (!species || typeof species !== 'string') {
        return res.status(400).json({
            success: false,
            error: 'Species is required and must be a string'
        });
    }
    
    if (typeof count !== 'number' || count < 0) {
        return res.status(400).json({
            success: false,
            error: 'Count must be a non-negative number'
        });
    }
    
    const bird = {
        id: nextId++,
        species,
        count: count || 0,
        weight: weight || 0
    };
    
    birds.push(bird);
    res.status(201).json({ success: true, data: bird });
});

// PUT update bird
app.put('/api/birds/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const index = birds.findIndex(b => b.id === id);
    
    if (index === -1) {
        return res.status(404).json({
            success: false,
            error: 'Bird not found'
        });
    }
    
    const { species, count, weight } = req.body;
    const bird = birds[index];
    
    if (species) bird.species = species;
    if (typeof count === 'number') bird.count = count;
    if (typeof weight === 'number') bird.weight = weight;
    
    birds[index] = bird;
    res.json({ success: true, data: bird });
});

// DELETE bird
app.delete('/api/birds/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const index = birds.findIndex(b => b.id === id);
    
    if (index === -1) {
        return res.status(404).json({
            success: false,
            error: 'Bird not found'
        });
    }
    
    const deleted = birds.splice(index, 1)[0];
    res.json({ success: true, data: deleted });
});

// ---- SEARCH ROUTE ----
app.get('/api/birds/search/:query', (req, res) => {
    const query = req.params.query.toLowerCase();
    const results = birds.filter(b => 
        b.species.toLowerCase().includes(query)
    );
    res.json({ success: true, data: results });
});

// ---- ERROR HANDLING ----
app.use((err, req, res, next) => {
    console.error('Error:', err.stack);
    res.status(500).json({
        success: false,
        error: 'Internal server error'
    });
});

// ---- 404 HANDLER ----
app.use((req, res) => {
    res.status(404).json({
        success: false,
        error: 'Route not found'
    });
});

// ---- START SERVER ----
app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

// ---- ROUTER MODULE ----
const birdRouter = express.Router();

birdRouter.get('/', (req, res) => {
    res.json(birds);
});

birdRouter.get('/:id', (req, res) => {
    const bird = birds.find(b => b.id === parseInt(req.params.id));
    bird ? res.json(bird) : res.status(404).send('Not found');
});

birdRouter.post('/', (req, res) => {
    const bird = { id: nextId++, ...req.body };
    birds.push(bird);
    res.status(201).json(bird);
});

// app.use('/birds', birdRouter);

Express routes handle HTTP methods. Middleware processes requests before routes. Error handling catches exceptions. Static files are served directly. Express simplifies server-side JavaScript development.

11.3 REST APIs

Design endpoints using HTTP methods: GET, POST, PUT, DELETE.

  • GET – Retrieve resources
  • POST – Create resources
  • PUT – Update resources (replace)
  • PATCH – Update resources (partial)
  • DELETE – Delete resources
  • Status codes – 200, 201, 400, 404, 500

Code Example:

const express = require('express');
const app = express();
app.use(express.json());

// ---- REST API DESIGN ----

// GET /birds - List all birds (with filtering)
app.get('/birds', (req, res) => {
    let result = [...birds];
    const { species, minWeight } = req.query;
    
    if (species) {
        result = result.filter(b => 
            b.species.toLowerCase().includes(species.toLowerCase())
        );
    }
    
    if (minWeight) {
        result = result.filter(b => b.weight >= parseFloat(minWeight));
    }
    
    res.json({
        count: result.length,
        data: result
    });
});

// GET /birds/:id - Get a single bird
app.get('/birds/:id', (req, res) => {
    const bird = birds.find(b => b.id === parseInt(req.params.id));
    if (!bird) {
        return res.status(404).json({ error: 'Bird not found' });
    }
    res.json(bird);
});

// POST /birds - Create a new bird
app.post('/birds', (req, res) => {
    const { species, count, weight } = req.body;
    
    // Validation
    if (!species || typeof species !== 'string') {
        return res.status(400).json({ error: 'Species is required' });
    }
    
    const newBird = {
        id: nextId++,
        species,
        count: count || 0,
        weight: weight || 0
    };
    
    birds.push(newBird);
    res.status(201).json(newBird);
});

// PUT /birds/:id - Replace an existing bird
app.put('/birds/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const index = birds.findIndex(b => b.id === id);
    
    if (index === -1) {
        return res.status(404).json({ error: 'Bird not found' });
    }
    
    const { species, count, weight } = req.body;
    if (!species) {
        return res.status(400).json({ error: 'Species is required' });
    }
    
    birds[index] = { id, species, count, weight };
    res.json(birds[index]);
});

// PATCH /birds/:id - Partially update a bird
app.patch('/birds/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const bird = birds.find(b => b.id === id);
    
    if (!bird) {
        return res.status(404).json({ error: 'Bird not found' });
    }
    
    const { species, count, weight } = req.body;
    if (species) bird.species = species;
    if (typeof count === 'number') bird.count = count;
    if (typeof weight === 'number') bird.weight = weight;
    
    res.json(bird);
});

// DELETE /birds/:id - Delete a bird
app.delete('/birds/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const index = birds.findIndex(b => b.id === id);
    
    if (index === -1) {
        return res.status(404).json({ error: 'Bird not found' });
    }
    
    birds.splice(index, 1);
    res.status(204).send();
});

// ---- BULK OPERATIONS ----

// POST /birds/bulk - Create multiple birds
app.post('/birds/bulk', (req, res) => {
    const { birds: newBirds } = req.body;
    if (!Array.isArray(newBirds) || newBirds.length === 0) {
        return res.status(400).json({ error: 'Birds array required' });
    }
    
    const created = newBirds.map(b => ({
        id: nextId++,
        species: b.species,
        count: b.count || 0,
        weight: b.weight || 0
    }));
    
    birds.push(...created);
    res.status(201).json({
        created: created.length,
        data: created
    });
});

// ---- RESPONSE FORMATS ----
function formatResponse(success, data, error = null) {
    return { success, data, error };
}

app.get('/birds/format', (req, res) => {
    res.json(formatResponse(true, birds));
});

// ---- PAGINATION ----
app.get('/birds/paginated', (req, res) => {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const start = (page - 1) * limit;
    const end = start + limit;
    
    const paginatedBirds = birds.slice(start, end);
    const total = birds.length;
    
    res.json({
        page,
        limit,
        total,
        pages: Math.ceil(total / limit),
        data: paginatedBirds
    });
});

REST APIs use HTTP methods for CRUD operations. GET retrieves data, POST creates, PUT updates, DELETE removes. Status codes indicate success or failure. Query parameters enable filtering and pagination. Response formats are consistent.

Testing

12.1 Jest

Jest is a testing framework often used with React.

  • Test functions – test() or it()
  • Assertions – expect() with matchers
  • Matchers – toBe(), toEqual(), toHaveLength()
  • Setup/Teardown – beforeEach(), afterEach()
  • Mocking – jest.fn(), jest.spyOn()
  • Coverage – Built-in coverage reports

Code Example:

// ---- FUNCTIONS TO TEST ----
function add(a, b) {
    return a + b;
}

function isEven(num) {
    return num % 2 === 0;
}

function getBirds() {
    return ['Eagle', 'Sparrow', 'Hawk'];
}

function fetchBirdData(id) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve({ id, species: 'Eagle', count: 3 });
        }, 100);
    });
}

// ---- JEST TESTS ----
describe('Math functions', () => {
    test('adds 1 + 2 to equal 3', () => {
        expect(add(1, 2)).toBe(3);
    });
    
    test('adds negative numbers correctly', () => {
        expect(add(-1, -2)).toBe(-3);
    });
});

describe('isEven function', () => {
    test('returns true for even numbers', () => {
        expect(isEven(2)).toBe(true);
        expect(isEven(4)).toBe(true);
        expect(isEven(0)).toBe(true);
    });
    
    test('returns false for odd numbers', () => {
        expect(isEven(1)).toBe(false);
        expect(isEven(3)).toBe(false);
    });
});

describe('getBirds function', () => {
    test('returns an array of birds', () => {
        const birds = getBirds();
        expect(Array.isArray(birds)).toBe(true);
        expect(birds).toHaveLength(3);
    });
    
    test('contains specific birds', () => {
        const birds = getBirds();
        expect(birds).toContain('Eagle');
        expect(birds).toContain('Sparrow');
        expect(birds).toContain('Hawk');
    });
});

// ---- ASYNC TESTS ----
describe('fetchBirdData', () => {
    test('returns bird data', async () => {
        const data = await fetchBirdData(1);
        expect(data).toEqual({ id: 1, species: 'Eagle', count: 3 });
    });
    
    test('handles callback with done', (done) => {
        fetchBirdData(1).then(data => {
            expect(data.id).toBe(1);
            done();
        });
    });
});

// ---- MOCKING ----
const mockApi = {
    get: jest.fn().mockResolvedValue({ data: { species: 'Eagle' } })
};

describe('API mocking', () => {
    test('mock API call', async () => {
        const result = await mockApi.get('/birds/1');
        expect(result.data.species).toBe('Eagle');
        expect(mockApi.get).toHaveBeenCalledWith('/birds/1');
        expect(mockApi.get).toHaveBeenCalledTimes(1);
    });
});

// ---- TESTING CLASSES ----
class BirdCounter {
    constructor() {
        this.count = 0;
    }
    
    increment() {
        this.count++;
        return this.count;
    }
    
    reset() {
        this.count = 0;
    }
}

describe('BirdCounter class', () => {
    let counter;
    
    beforeEach(() => {
        counter = new BirdCounter();
    });
    
    test('starts at 0', () => {
        expect(counter.count).toBe(0);
    });
    
    test('increments count', () => {
        counter.increment();
        expect(counter.count).toBe(1);
        counter.increment();
        expect(counter.count).toBe(2);
    });
    
    test('resets count', () => {
        counter.increment();
        counter.reset();
        expect(counter.count).toBe(0);
    });
});

// ---- SNAPSHOT TESTING ----
test('bird list snapshot', () => {
    const birds = getBirds();
    expect(birds).toMatchSnapshot();
});

// ---- EXCEPTION TESTING ----
function divide(a, b) {
    if (b === 0) {
        throw new Error('Division by zero');
    }
    return a / b;
}

test('divides by zero', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero');
});

Jest provides describe for grouping tests. test defines individual tests. expect makes assertions with matchers. Async tests use async/await or done. Mocks replace dependencies. Coverage reports show tested code.

Build Tools

13.1 Webpack

Webpack bundles JavaScript, CSS, and assets using loaders and plugins.

  • Entry – Entry point file
  • Output – Bundled output file
  • Loaders – Transform files (CSS, images, Babel)
  • Plugins – Additional functionality (minification, HTML generation)
  • Mode – Development or production
  • Dev Server – Development server with hot reload

Code Example:

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = (env, argv) => {
    const isProduction = argv.mode === 'production';
    
    return {
        entry: './src/index.js',
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: isProduction ? '[name].[contenthash].js' : '[name].js',
            chunkFilename: isProduction ? '[id].[chunkhash].js' : '[id].js'
        },
        mode: isProduction ? 'production' : 'development',
        devtool: isProduction ? 'source-map' : 'eval-source-map',
        
        module: {
            rules: [
                // JavaScript
                {
                    test: /\.jsx?$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env', '@babel/preset-react']
                        }
                    }
                },
                // CSS
                {
                    test: /\.css$/,
                    use: [
                        isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
                        'css-loader',
                        'postcss-loader'
                    ]
                },
                // SCSS
                {
                    test: /\.scss$/,
                    use: [
                        isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
                        'css-loader',
                        'sass-loader'
                    ]
                },
                // Images
                {
                    test: /\.(png|jpe?g|gif|svg)$/,
                    type: 'asset/resource'
                },
                // Fonts
                {
                    test: /\.(woff|woff2|eot|ttf|otf)$/,
                    type: 'asset/resource'
                }
            ]
        },
        
        resolve: {
            extensions: ['.js', '.jsx', '.json'],
            alias: {
                '@': path.resolve(__dirname, 'src'),
                '@components': path.resolve(__dirname, 'src/components'),
                '@utils': path.resolve(__dirname, 'src/utils')
            }
        },
        
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
                filename: 'index.html',
                minify: isProduction
            }),
            isProduction && new MiniCssExtractPlugin({
                filename: '[name].[contenthash].css',
                chunkFilename: '[id].[chunkhash].css'
            })
        ].filter(Boolean),
        
        optimization: {
            minimize: isProduction,
            minimizer: [
                new TerserPlugin({
                    terserOptions: {
                        compress: {
                            drop_console: isProduction
                        }
                    }
                })
            ],
            splitChunks: {
                chunks: 'all',
                cacheGroups: {
                    vendor: {
                        test: /[\\/]node_modules[\\/]/,
                        name: 'vendors',
                        chunks: 'all'
                    }
                }
            }
        },
        
        devServer: {
            static: {
                directory: path.join(__dirname, 'dist')
            },
            port: 3000,
            hot: true,
            open: true,
            historyApiFallback: true,
            proxy: {
                '/api': {
                    target: 'http://localhost:5000',
                    pathRewrite: { '^/api': '' }
                }
            }
        }
    };
};

Webpack bundles code with entry and output. Loaders transform files. Plugins add functionality. Development server serves files with hot reload. Production mode optimizes bundles.

13.5 Vite

Vite is a next-generation build tool for modern web projects.

  • Fast startup – ES modules in development
  • HMR – Hot Module Replacement
  • Build – Rollup-based production build
  • Plugins – Rich plugin ecosystem
  • Framework support – React, Vue, Svelte

Code Example:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
    plugins: [
        react({
            fastRefresh: true
        })
    ],
    
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
            '@components': path.resolve(__dirname, './src/components'),
            '@utils': path.resolve(__dirname, './src/utils')
        }
    },
    
    server: {
        port: 3000,
        open: true,
        proxy: {
            '/api': {
                target: 'http://localhost:5000',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
    },
    
    build: {
        outDir: 'dist',
        sourcemap: true,
        minify: 'terser',
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['react', 'react-dom'],
                    router: ['react-router-dom']
                }
            }
        }
    },
    
    css: {
        preprocessorOptions: {
            scss: {
                additionalData: '@import "@/styles/variables.scss";'
            }
        },
        modules: {
            localsConvention: 'camelCase'
        }
    },
    
    optimizeDeps: {
        include: ['react', 'react-dom', '@emotion/react']
    },
    
    envPrefix: 'APP_'
});

// ---- TYPESCRIPT CONFIG ----
// tsconfig.json
{
    "compilerOptions": {
        "target": "ESNext",
        "useDefineForClassFields": true,
        "lib": ["DOM", "DOM.Iterable", "ESNext"],
        "allowJs": false,
        "skipLibCheck": true,
        "esModuleInterop": false,
        "allowSyntheticDefaultImports": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "module": "ESNext",
        "moduleResolution": "Node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        "jsx": "react-jsx",
        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"],
            "@components/*": ["src/components/*"],
            "@utils/*": ["src/utils/*"]
        }
    },
    "include": ["src"],
    "references": [{ "path": "./tsconfig.node.json" }]
}

Vite serves ES modules directly for fast development. HMR updates modules without reloading. Production builds use Rollup. Plugins extend functionality. Configuration is simple and intuitive.

Career Readiness

20.1 Portfolio Projects

Build and showcase projects to demonstrate JavaScript skills.

Project Ideas:

  1. Weather App – React + OpenWeatherMap API
  2. Task Manager – React + LocalStorage
  3. Blog Platform – React + Node.js + MongoDB
  4. Real-time Chat – React + WebSocket + Node.js
  5. E-commerce Store – React + Redux + Express
  6. Portfolio Website – HTML + CSS + JavaScript
  7. Movie Database – React + TMDB API
  8. Crypto Tracker – React + WebSocket + Chart.js
  9. Social Media Dashboard – React + D3.js
  10. Music Player – React + Web Audio API

Code Example (Project README):

# Bird Tracker - Full Stack JavaScript Project

## 🚀 Features
- 📊 Dashboard with bird statistics
- 🔍 Search and filter birds
- ✏️ CRUD operations for bird data
- 📱 Responsive design
- 🔒 User authentication (JWT)
- 📈 Charts and visualizations

## 🛠️ Tech Stack
### Frontend
- React 18
- Redux Toolkit (state management)
- Tailwind CSS (styling)
- React Router (routing)
- Chart.js (visualizations)
- Axios (HTTP client)
- Jest + React Testing Library (testing)

### Backend
- Node.js + Express
- MongoDB with Mongoose
- JWT authentication
- REST API
- Joi validation
- Winston logging

## 📁 Project Structure

bird-tracker/
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── store/
│ │ ├── utils/
│ │ └── styles/
│ ├── public/
│ ├── tests/
│ └── package.json
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ ├── models/
│ │ ├── routes/
│ │ ├── middleware/
│ │ ├── services/
│ │ ├── config/
│ │ └── utils/
│ ├── tests/
│ └── package.json
├── docker-compose.yml
└── README.md


## 🚀 Getting Started
### Prerequisites
- Node.js 18+
- MongoDB 6+
- npm or yarn

### Installation
```bash
# Clone repository
git clone https://github.com/username/bird-tracker.git

# Install frontend dependencies
cd bird-tracker/frontend
npm install

# Install backend dependencies
cd ../backend
npm install

# Set up environment variables
cp .env.example .env
# Edit .env with your values

# Start development servers
cd ../frontend
npm run dev
# Backend
cd ../backend
npm run dev

📊 API Endpoints

MethodEndpointDescription
GET/api/birdsGet all birds
GET/api/birds/:idGet a bird
POST/api/birdsCreate a bird
PUT/api/birds/:idUpdate a bird
DELETE/api/birds/:idDelete a bird
GET/api/birds/searchSearch birds
GET/api/birds/statsGet statistics

🧪 Testing

# Frontend tests
cd frontend
npm test

# Backend tests
cd backend
npm test

🐳 Docker

# Build and run
docker-compose up

# Stop
docker-compose down

**How It Works**

Portfolio projects demonstrate real-world JavaScript skills. README files document projects clearly. Projects show full-stack capabilities. Testing ensures quality. Deployment shows production readiness.

---

### 20.5 Professional Development

Stay updated with the JavaScript ecosystem.

**Learning Resources:**

**Documentation:**
- MDN Web Docs - Official JavaScript reference
- JavaScript.info - Comprehensive tutorial

**Courses:**
- Frontend Masters - Advanced JavaScript courses
- Udemy - Practical JavaScript courses

**Blogs:**
- JavaScript Weekly - Weekly newsletter
- Dev.to - Community blog
- Medium - JavaScript articles

**Conferences:**
- JSConf - JavaScript conferences worldwide
- React Conf - React conference
- Node.js Interactive - Node.js conference

**Podcasts:**
- Syntax - Web development podcast
- JS Party - JavaScript podcast
- Full Stack Radio - Full-stack development

**Communities:**
- Stack Overflow - Q&A
- GitHub - Open source
- Discord/Reddit - Community discussions

**Code Example (Learning Plan):**

```javascript
// 6-Month JavaScript Learning Plan
const learningPlan = {
    months: {
        month1: {
            focus: "JavaScript Fundamentals",
            topics: [
                "Variables and Data Types",
                "Functions and Scope",
                "Arrays and Objects",
                "Loops and Conditionals"
            ],
            projects: ["To-Do List", "Calculator"]
        },
        month2: {
            focus: "Advanced JavaScript",
            topics: [
                "Closures and Hoisting",
                "Promises and Async/Await",
                "ES6+ Features",
                "Error Handling"
            ],
            projects: ["Weather App", "API Integration"]
        },
        month3: {
            focus: "React Fundamentals",
            topics: [
                "Components and Props",
                "State and Hooks",
                "Lifecycle Methods",
                "Routing"
            ],
            projects: ["Blog App", "E-commerce Store"]
        },
        month4: {
            focus: "Node.js Backend",
            topics: [
                "HTTP Server",
                "Express.js",
                "REST API Design",
                "Database Integration"
            ],
            projects: ["REST API", "Authentication"]
        },
        month5: {
            focus: "Full Stack Development",
            topics: [
                "React + Node.js Integration",
                "State Management (Redux)",
                "Testing (Jest)",
                "Deployment"
            ],
            projects: ["Full Stack App", "Real-time Chat"]
        },
        month6: {
            focus: "Career Preparation",
            topics: [
                "Portfolio Building",
                "Open Source Contributions",
                "Interview Preparation",
                "System Design"
            ],
            projects: ["Portfolio Website", "Open Source PR"]
        }
    },
    
    dailyRoutine: {
        morning: "Read article or watch tutorial (30 min)",
        day: "Practice coding (2-3 hours)",
        evening: "Review and document (30 min)",
        weekly: "Build project and write blog post"
    }
};

// Progress tracking
function trackProgress(day, completed = true) {
    const dailyTasks = [
        "Read documentation",
        "Write code",
        "Review concepts",
        "Project work",
        "Community engagement"
    ];
    
    console.log(`Day ${day}: ${completed ? '✅' : '❌'}`);
    if (completed) {
        console.log("Completed tasks:");
        dailyTasks.forEach(task => console.log(`  - ${task}`));
    }
}

// Continuous learning
function continueLearning() {
    console.log("Stay updated with:");
    console.log("- Follow JavaScript blogs");
    console.log("- Attend conferences");
    console.log("- Contribute to open source");
    console.log("- Build side projects");
    console.log("- Network with developers");
}

trackProgress(1);
continueLearning();

Professional development requires continuous learning. Follow documentation and courses. Engage with the community. Build projects to apply knowledge. Stay updated with ecosystem changes.

Final Advice

JavaScript feels approachable at first but reveals depth as you explore. The ecosystem is vast and ever-evolving, which makes it both exciting and challenging. Every year brings new features, frameworks, and tools that push the boundaries of what’s possible in web development.

Start with the first stage today by running “Hello, World!” on your own computer, using your own terminal and hands-on practice. Then break it on purpose—remove a semicolon, misspell console.log, forget a closing brace—and read the resulting error message slowly and carefully instead of panicking.

That single habit, repeated consistently over weeks and months, is genuinely how every strong JavaScript programmer built their foundation.

Use the AI prompts provided for each concept. They’re designed to give you code examples, clear explanations, and practical exercises. Copy them into your favorite AI assistant and work through the examples yourself. Write the code, run it, modify it, break it, and fix it.

The JavaScript ecosystem is vast, but you don’t need to learn everything at once. Focus on the fundamentals first, then explore areas that interest you. Build projects that solve real problems. Contribute to open source. Stay curious and keep learning.

Good luck, and welcome to JavaScript. The journey is challenging, but the destination is worth it.

Scroll to Top