Runtime Architecture

1. The Single-Threaded Core

At its heart, the JavaScript engine is synchronous and single-threaded.

  • One Thread: It has exactly one Main Thread of execution.
  • One Call Stack: It has exactly one stack to track function calls.

This means JavaScript, by itself, is linear. It executes code line-by-line. If the stack is occupied, nothing else can run.


2. The Blocking Problem

Blocking occurs when a heavy computation is performed in the Call Stack. While the Stack is busy, the application is paralyzed.

  • The Freeze: The browser cannot repaint the screen, render animations, or handle clicks because the Main Thread is stuck in a loop.
function heavyTask() {
  const end = Date.now() + 5000;
  while (Date.now() < end) {
    /* CPU is trapped here for 5s */
  }
}
 
heavyTask();
console.log("This waits 5 seconds to run");

3. The Asynchronous Solution: Task Outsourcing

JavaScript handles slow tasks by outsourcing them to the Host Environment (the Browser or Node.js). The Runtime is a collaboration between three distinct pillars:

A. The JS Engine (Synchronous)

  • Call Stack: Executes code.
  • Memory Heap: Allocates memory.

B. The Environment APIs (Multi-threaded)

These are threads provided by the browser (Web APIs) or Node.js (C++ APIs). They handle the waiting in the background without blocking the Call Stack.

  • DOM Events: Clicks, scrolls.
  • Network: fetch or XHR requests.
  • Timers: setTimeout and setInterval.

C. The Glue: The Two Queues & The Event Loop

The environment doesn't push code back to the stack directly. It places callbacks into one of two specialized queues.

  1. The Microtask Queue (Job Queue):
  • Priority: High.
  • Sources: Promise callbacks (.then, .catch), queueMicrotask, MutationObserver.
  • Behavior: The Event Loop flushes the entire microtask queue immediately after the current stack is empty, before doing anything else.
  1. The Macrotask Queue (Task Queue):
  • Priority: Standard.
  • Sources: setTimeout, setInterval, setImmediate, I/O, UI Rendering.
  • Behavior: The Event Loop picks one task from here only after the Microtask Queue is completely empty.

4. The Event Loop Algorithm

The Event Loop is a continuously running process that handles execution of callback. Follows the following order :

  1. Execute Stack: Run synchronous code until the Call Stack is empty.
  2. Flush Microtasks: Run all tasks in the Microtask Queue. If a microtask adds another microtask, run that too.
  3. Render: (Browser only) Perform UI updates/repaints.
  4. Run Macrotask: Pick one task from the Macrotask Queue and push it to the Stack.
  5. Repeat: Go back to Step 1.

Summary Table

ComponentResponsibilityThreadingPriority
Call StackExecutes JS logicSingle ThreadImmediate
Environment APIsBackground waitingMulti-threadedN/A
Microtask QueuePromisesPassive QueueHigh (Run All)
Macrotask QueueTimers / EventsPassive QueueLow (Run One)
Event LoopOrchestrationContinuous LoopMonitor

Stop and Think: Is setTimeout(fn, 0) immediate?

Answer: No. Even with 0ms, the callback is placed in the Macrotask Queue. The Event Loop must first finish the current script, then flush the entire Microtask Queue, and then handle rendering before it finally picks up your setTimeout callback.