Week 2: The JavaScript Runtime

JavaScriptFrontendTutorialReact

A deep dive into JavaScript runtime fundamentals for React developers. Covers the event loop, asynchronous operations, promises, DOM manipulation, and functional programming concepts essential for senior-level frontend engineering.


Modern frontend engineering is often just an exercise in abstraction. We build complex UIs using React, relying on its "magic" to handle rendering and state. But when the app freezes, the magic dies.

When performance bottlenecks ariseβ€”slow interactions, memory leaks, or UI jankβ€”the abstraction fails, and the underlying reality of the JavaScript Runtime takes over. To fix these issues, you cannot just be a React developer. You must be a JavaScript engineer.

This guide strips away the framework to expose the engine underneath. We will dissect the Single Thread, the Event Loop, and the raw DOM APIs that dictate how your application actually performs.

1. The Root Cause of Frontend Struggles

The most common reason developers struggle with React is not because React is hard. It is because they do not understand JavaScript.

I see this constantly in code reviews and debugging sessions. Developers learn syntaxβ€”how to write a function or a for loopβ€”but they do not understand the Runtime.

They treat asynchronous operations like magic, the DOM like a black box, and assume code executes strictly from top to bottom. To operate at a Senior Frontend Engineer level, you must understand the environment your code lives in. You need to understand:

  1. How the browser processes tasks.
  2. How it handles concurrency in a single-threaded language.
  3. How to manipulate the Document Object Model efficiently.

This week, I am stripping away the frameworks. We are going deep into the engine.


2. The Event Loop & The Single Thread

JavaScript is single-threaded. This is a non-negotiable constraint of the language. It has one Call Stack and one Memory Heap. It can only do one thing at a time.

πŸ›‘ Constraint: If function A is running, function B cannot run until A finishes.

This raises an obvious question: How does a browser handle a fetch request, a timeout, and a user click simultaneously without freezing the UI?

The answer is the Event Loop.

The Event Loop is the coordinator between the Call Stack (where your code runs) and the Browser implementation (Web APIs). To debug performance issuesβ€”like Aadhar eSign integrations or heavy dashboard renderingβ€”you must map these components mentally:

🧩 The Components

ComponentDescription
The Call StackA LIFO (Last In, First Out) structure tracking program execution. Function calls push; returns pop.
Web APIsNot JavaScript. These are C++ APIs (Chrome/V8) like setTimeout, fetch, and DOM events. JS hands off tasks here immediately.
Callback QueueAlso known as Macrotasks. When a Web API finishes, the callback lands here.
Microtask QueueThe VIP line. Higher priority than Macrotasks. Includes Promises (then, catch, await) and queueMicrotask.

πŸ”„ The Algorithm

The Event Loop is a simple while-loop algorithm:

  1. Is the Call Stack empty?
  2. If NO: Wait.
  3. If YES: Check the Microtask Queue.
  4. Run all Microtasks until the queue is empty.
  5. Then, move one item from the Callback Queue to the Call Stack.

🧠 The "Gotcha" Interview Question

I use this example to test understanding of execution order. Predict the logs:

console.log('1: Start');

setTimeout(() => {
  console.log('2: Timeout');
}, 0);

Promise.resolve().then(() => {
  console.log('3: Promise');
});

console.log('4: End');

The Verdict

  • ❌ The Junior Answer: 1, 4, 2, 3. (Assumes setTimeout is faster/simpler than Promise).
  • βœ… The Senior Answer: 1, 4, 3, 2.

The Mechanics:

  1. 1: Start hits the stack and logs immediately.
  2. setTimeout is sent to Web APIs. It finishes (0ms) and pushes callback to Macrotask Queue.
  3. Promise resolves and pushes then callback to Microtask Queue.
  4. 4: End hits the stack and logs immediately.
  5. Stack is empty. Event Loop checks Microtasks. Logs 3: Promise.
  6. Microtasks empty. Event Loop checks Macrotasks. Logs 2: Timeout.

πŸ’‘ Why this matters for React: React 18's automatic batching relies on Microtask prioritization. If you flood the Microtask queue (e.g., infinite recursion in a Promise), you will freeze the browser just as badly as a while(true) loop.


3. Asynchronous Mastery: Promises over Callbacks

Callbacks are dead. Long live Promises.

The Problem with Callbacks

In the past, we nested callbacks ("Callback Hell"). Error handling was impossible because try/catch blocks strictly work for synchronous code in the Call Stack. They cannot catch errors inside a setTimeout callback that runs later.

Promises & Async/Await

Async/Await is "Syntactic Sugar" over Promises, but it fundamentally changes readability and error handling. It makes async code look synchronous.

πŸ“‰ The "Waterfall" Performance Killer

A common mistake when moving to await is creating unnecessary waterfalls.

πŸ”΄ BAD: Sequential Waiting

async function getUserData(userId) {
  const profile = await fetchProfile(userId);   // ⏳ Waits 1s
  const posts = await fetchPosts(userId);       // ⏳ Waits 1s
  const settings = await fetchSettings(userId); // ⏳ Waits 1s
  // Total time: 3s
}

🟒 GOOD: Concurrency

async function getUserData(userId) {
  // Trigger all requests immediately (in parallel) πŸš€
  const profilePromise = fetchProfile(userId);
  const postsPromise = fetchPosts(userId);
  const settingsPromise = fetchSettings(userId);

  // Wait for all to finish
  const [profile, posts, settings] = await Promise.all([
    profilePromise,
    postsPromise,
    settingsPromise,
  ]);
  // Total time: 1s (assuming they all take 1s)
}

⚠️ Critical Concept: Promise.all fails fast. If one promise rejects, the entire aggregate rejects. If you need partial success, use Promise.allSettled().


4. DOM Manipulation (The Real API)

React is an abstraction. It generates a Virtual DOM and syncs it to the Real DOM. To understand the cost, you must understand the underlying API.

Selecting Elements

  • getElementById / getElementsByClassName: Returns "Live" collections (HTMLCollection). Updates automatically if DOM changes. Fast, but confusing.
  • querySelector / querySelectorAll: Returns "Static" collections (NodeList). Uses CSS selector syntax. This is the standard.

Event Propagation: Bubbling vs. Capturing

When you click a <button> inside a <div>:

  1. ⬇️ Capturing Phase: Window β†’ Document β†’ Div β†’ Button.
  2. 🎯 Target Phase: The event hits the button.
  3. ⬆️ Bubbling Phase: Button β†’ Div β†’ Document β†’ Window. (Most events bubble).

event.stopPropagation() exists to kill the bubble.

⚑ Why React Uses "Synthetic Events"

React attaches one single event listener to the root of your document (Event Delegation). When you click a button, React catches the event at the root and determines which component "clicked."

Event Delegation (Vanilla Optimization):

// πŸ”΄ High Memory Usage (Listener per row)
document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', (e) => {
    console.log('Clicked', e.target.innerText);
  });
});

// 🟒 Event Delegation (One Listener)
document.querySelector('ul').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log('Clicked', e.target.innerText);
  }
});

5. Modern Functional Logic (ES6+)

React relies heavily on functional programming. Stop writing for loops.

Imperative vs. Declarative

  • Imperative (How): "Create array. Loop. If even, push to array."
  • Declarative (What): "Filter for evens."

The Holy Trinity: Map, Filter, Reduce

  1. .map()
    • Action: Transforms data. Returns new array (same length).
    • React Use Case: Rendering lists.
  2. .filter()
    • Action: Selects data. Returns new array (smaller/equal length).
    • React Use Case: Deleting an item.
  3. .reduce()
    • Action: Synthesizes data. Returns anything.
    • React Use Case: Calculating totals or transforming Array β†’ Object/Map.

Performance Optimization with Reduce

Looking up a user in an array is O(N). Looking it up in an object is O(1).

const users = [
  { id: 'u1', name: 'Alice' },
  { id: 'u2', name: 'Bob' }
];

// Challenge: Convert to an object where keys are IDs
const userMap = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {});

// Result: { u1: { name: 'Alice' }, u2: { name: 'Bob' } }

Immutability

In React, you never mutate state directly (state.value = 5). You replace it.

// ❌ Mutation
const user = { name: 'Alice' };
user.name = 'Bob';

// βœ… Immutability (Spread Operator)
const newUser = { ...user, name: 'Charlie' };

Note: newUser is a different reference than user__. React compares references to decide when to re-render.


6. Practical Application: Boss Fights

To truly master these concepts, I recommend building implementations that force you to use the raw APIs.

πŸ₯Š Challenge 1: The Event Loop Visualizer

Goal: Prove you understand execution order. Create an HTML page that triggers and logs the following in the exact browser execution order:

  • A synchronous log.
  • A setTimeout (Macrotask) log.
  • A Promise.resolve (Microtask) log.
  • A requestAnimationFrame (Render step) log.
  • A synchronous heavy calculation (blocking loop).

image.png

πŸ—οΈ Challenge 2: Vanilla Drag-and-Drop

Goal: Master DOM API without libraries. Build a "Kanban Board" (To Do, In Progress, Done).

  • Use draggable="true".
  • Use dragstart to store ID in dataTransfer.
  • Use dragover (must call e.preventDefault()).
  • Use drop to append element to new column.
  • (optional) : make use of localStorage to have persistance.

image.png

Here is a Starter Kit for Week 2.


πŸ“‚ File Structure

week-2/
β”œβ”€β”€ 01-event-loop/
β”‚   β”œβ”€β”€ index.html
β”‚   └── script.js
β”œβ”€β”€ 02-kanban-board/
β”‚   β”œβ”€β”€ index.html
β”‚   β”œβ”€β”€ styles.css
β”‚   └── script.js
└── README.md

πŸ₯Š Project 1: Event Loop Visualizer (Starter)

Goal: Fill in the JavaScript to demonstrate the exact order of execution: Sync β†’ Microtasks β†’ Macrotasks β†’ Render.

01-event-loop/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Starter: Event Loop Visualizer</title>
    <style>
        body { font-family: monospace; padding: 2rem; background: #111; color: #eee; }
        .btn { padding: 10px 20px; font-size: 1rem; cursor: pointer; background: #007bff; color: white; border: none; }
        .log-container { margin-top: 20px; border: 1px solid #333; padding: 1rem; min-height: 200px; }
        .log-item { padding: 5px; border-bottom: 1px solid #222; }

        /* CSS Classes for different log types */
        .sync { color: #4caf50; }  /* Green */
        .micro { color: #ffeb3b; } /* Yellow */
        .macro { color: #f44336; } /* Red */
    </style>
</head>
<body>
    <h1>Event Loop Race</h1>
    <button id="run-btn" class="btn">πŸƒ Run Race</button>
    <div id="logs" class="log-container"></div>

    <script src="script.js"></script>
</body>
</html>

01-event-loop/script.js

const logContainer = document.getElementById('logs');
const runBtn = document.getElementById('run-btn');

// Helper: Adds a colored log line to the DOM
function addLog(text, type) {
    const div = document.createElement('div');
    div.className = `log-item ${type}`;
    div.innerText = `[${type.toUpperCase()}] ${text}`;
    logContainer.appendChild(div);
    console.log(text);
}

runBtn.addEventListener('click', () => {
    logContainer.innerHTML = ''; // Clear previous logs

    // TODO 1: Log a synchronous start message
    // Hint: Use addLog('Message', 'sync')

    // TODO 2: Schedule a Macrotask (setTimeout)
    // Hint: Delay of 0ms. Use type 'macro'.

    // TODO 3: Schedule a Microtask (Promise)
    // Hint: Promise.resolve().then(...). Use type 'micro'.

    // TODO 4: (Optional) Schedule a Request Animation Frame
    // Hint: requestAnimationFrame(...). Use type 'raf'.

    // TODO 5: Log a synchronous end message
    // Hint: Use type 'sync'.
});

πŸ₯Š Project 2: Vanilla Kanban Board (Starter)

Goal: Implement Drag and Drop using the native HTML5 API. πŸ† Bonus: Persist the board state using localStorage.

02-kanban-board/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Starter: Vanilla Kanban</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="board">
        <!-- Column 1: TODO -->
        <div class="column" id="todo" data-status="todo">
            <h3>πŸ“ To Do</h3>
            <!-- Cards go here -->
            <div class="card" draggable="true" id="c1">Master Event Loop</div>
        </div>

        <!-- Column 2: IN PROGRESS -->
        <div class="column" id="inprogress" data-status="inprogress">
            <h3>🚧 In Progress</h3>
            <div class="card" draggable="true" id="c2">Build Kanban</div>
        </div>

        <!-- Column 3: DONE -->
        <div class="column" id="done" data-status="done">
            <h3>βœ… Done</h3>
        </div>
    </div>

    <!-- Controls for LocalStorage Bonus -->
    <div style="text-align:center; margin-top: 20px; color: white;">
        <button onclick="localStorage.clear(); window.location.reload();">πŸ—‘οΈ Clear LocalStorage</button>
    </div>

    <script src="script.js"></script>
</body>
</html>

02-kanban-board/styles.css

:root { --bg: #222; --column: #ebecf0; --card: #fff; }
body { font-family: sans-serif; background: var(--bg); display: flex; flex-direction: column; align-items: center; padding-top: 50px; }
.board { display: flex; gap: 20px; }

.column {
    width: 250px;
    background: var(--column);
    border-radius: 8px;
    padding: 10px;
    min-height: 400px;
    display: flex; flex-direction: column; gap: 10px;
}

.card {
    background: var(--card);
    padding: 15px;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.12);
    cursor: grab;
}

/* Visual Feedback Classes */
.dragging { opacity: 0.5; border: 2px dashed #666; }
.drag-over { background: #d0d4e0; outline: 2px dashed #007bff; }

02-kanban-board/script.js

/* =========================================
   PART 1: DRAG AND DROP LOGIC
   ========================================= */

const draggables = document.querySelectorAll('.card');
const columns = document.querySelectorAll('.column');

// 1. Setup Drag Listeners for Cards
draggables.forEach(card => {
    card.addEventListener('dragstart', (e) => {
        // TODO: Add 'dragging' class to the card
        // TODO: Store the card's ID in e.dataTransfer.setData()
    });

    card.addEventListener('dragend', (e) => {
        // TODO: Remove 'dragging' class
    });
});

// 2. Setup Drop Zones for Columns
columns.forEach(column => {
    column.addEventListener('dragover', (e) => {
        // TODO: Prevent default behavior (Critical for dropping!)
        // TODO: Add visual cue (class 'drag-over')
    });

    column.addEventListener('dragleave', () => {
        // TODO: Remove visual cue
    });

    column.addEventListener('drop', (e) => {
        e.preventDefault();
        // TODO: Remove visual cue

        // TODO: Get the card ID from e.dataTransfer.getData()
        // TODO: Find the element by ID and append it to this column

        // OPTIONAL: Call saveBoard() here to save changes
        // saveBoard();
    });
});

/* =========================================
   PART 2: LOCAL STORAGE (OPTIONAL TASK)
   ========================================= */

/*
  TODO: Implement saveBoard()
  1. Loop through all columns.
  2. For each column, map over its children (.card) to get their text/IDs.
  3. Create an object like: { todo: ['Item 1'], done: ['Item 2'] }
  4. Save to localStorage using JSON.stringify()
*/
function saveBoard() {
    console.log("Saving board state...");
}

/*
  TODO: Implement loadBoard()
  1. Check if localStorage has data.
  2. If yes, JSON.parse() it.
  3. Clear the current columns in the DOM.
  4. Re-create the cards programmatically based on the saved data.
  5. Don't forget to re-attach event listeners to new cards!
*/
function loadBoard() {
    console.log("Loading board state...");
}

// Check on startup
// window.addEventListener('DOMContentLoaded', loadBoard);

🧠 The "Optional Task" Guide

If you want to implement the Local Storage feature in the Kanban board, here is the mental model you need:

  1. State is Truth: Right now, the DOM is your "database". When you refresh, the DOM resets. To fix this, you need a Data Object to be the single source of truth.
  2. Serialization: localStorage only stores strings.
    • Save: Object β†’ JSON.stringify β†’ localStorage
    • Load: localStorage β†’ JSON.parse β†’ Object
  3. Rehydration: When the page loads, if data exists, you must delete the hardcoded HTML cards and create new DOM elements based on your data.

Hint for recreating cards dynamically:

function createCard(text) {
    const div = document.createElement('div');
    div.classList.add('card');
    div.draggable = true;
    div.innerText = text;
    // Important: You must add the dragstart/end listeners
    // to this new element because it wasn't there on page load!
    return div;
}

Next Up: We leave the "Wild West" of loose JavaScript behind. In Week 3, we install the Compiler. We start our journey into TypeScript.