Search Knowledge

© 2026 LIBREUNI PROJECT

Modern JavaScript & Ecosystem / Asynchronous JavaScript

The Runtime and The Event Loop

Single-Threaded Nature

JavaScript is a single-threaded language, meaning it has one Call Stack and can execute only one command at a time. However, modern web applications handle multiple network requests, user interactions, and timers simultaneously. This is achieved through the Concurrency Model and the Event Loop, which allow JavaScript to perform non-blocking I/O even while running on a single thread.

The Components of the Runtime

The JavaScript runtime is composed of several key parts:

  1. Memory Heap: Where memory allocation happens (objects, variables).
  2. Call Stack: Where the engine keeps track of function execution (LIFO - Last In, First Out).
  3. Web APIs / Node APIs: External environments (provided by the browser or Node.js) that handle things like setTimeout, DOM events, and fetch requests.
  4. Callback Queue (Task Queue): Where callbacks from Web APIs wait to be pushed onto the stack.
  5. Microtask Queue: A high-priority queue for Promises and process.nextTick.

The Mechanics of the Event Loop

The Event Loop has a very simple job: monitor the Call Stack and the Task Queues. If the Call Stack is empty, it takes the first task from the queue and pushes it onto the stack, which effectively runs it.

The Execution Order

  1. Execute all synchronous code in the Call Stack.
  2. Check the Microtask Queue. Execute all pending microtasks until the queue is empty.
  3. Check the Callback Queue (Macrotasks). Execute the first task in the queue.
  4. Repeat.

This means that microtasks (like Promise resolutions) are always executed before the next macrotask (like setTimeout), even if the macrotask was scheduled first.

Code
skinparam componentStyle rectangle

component "Call Stack" as stack
component "Microtask Queue" as micro
component "Callback Queue" as macro
actor "Event Loop" as loop

stack --> loop : "Empty?"
loop --> micro : "Drain All"
micro --> stack : "Run"
loop --> macro : "Run One"
macro --> stack : "Run"

note bottom of micro : Promises
note bottom of macro : setTimeout, etc
Call StackMicrotask QueueCallback QueueEvent LoopPromisessetTimeout, etcEmpty?Drain AllRunRun OneRun

Blocking the Event Loop

Since the Event Loop can only process one thing at a time, performing a heavy computation (like calculating prime numbers or processing a large image) synchronously will “block” the event loop. While the loop is blocked, the browser cannot render updates, handle user input, or process callbacks. This results in a “frozen” user interface.

Visualizing Asynchrony

Consider the following code:

console.log("Start");

setTimeout(() => {
  console.log("Timeout");
}, 0);

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

console.log("End");

Output sequence:

  1. Start (Synchronous)
  2. End (Synchronous)
  3. Promise (Microtask - higher priority)
  4. Timeout (Macrotask - lower priority)

Even though setTimeout has a delay of 0 ms, its callback is placed in the Macrotask queue, which must wait for the current execution and the entire Microtask queue to finish.

If the Call Stack is busy with a long-running while loop, when will a setTimeout callback execute?