Skip to main content
Knowledge Hub

Event Loop and Web APIs

How JavaScript handles asynchronous operations with a single thread

Last updated: April 2, 2025

JavaScript is single-threaded, meaning it can only execute one task at a time on the main thread. There is only one call stack. But JavaScript can still handle asynchronous tasks like fetching data, timers, and UI events using the event loop, Web APIs, and callback queues.

Understanding how these components work together is essential for writing non-blocking, responsive applications.

The Key Players

There are five major components you need to understand:

1. Call Stack
2. Heap
3. Web APIs
4. Callback / Task Queue
5. Event Loop

Call Stack (Execution Context Stack)

The call stack is managed by the JavaScript Engine (like V8). It’s where functions get pushed (called) and popped (returned). Only one function executes at a time.

function greet() {
  console.log("Hello");
}
greet();

Execution steps:

push greet() → execute → pop greet()

If the call stack is full (too many nested calls), you get a Stack Overflow error.

Heap

The heap is managed by the JavaScript Engine for memory allocation. Objects, arrays, and closures are stored here dynamically. It is used by the garbage collector for memory management.

Web APIs

Web APIs are browser-provided features, not part of the JavaScript language itself. They run outside the JavaScript engine, allowing asynchronous operations.

When you call something like setTimeout(), fetch(), addEventListener(), XMLHttpRequest(), DOM events, or Geolocation API, these functions are handled by the browser’s Web API environment, not the JavaScript engine.

Example

console.log("Start");

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

console.log("End");

Step-by-Step Process

  1. console.log(“Start”) executes immediately (stack)
  2. setTimeout() is passed to Web API, timer runs in browser (not blocking JS)
  3. console.log(“End”) executes immediately
  4. After 2 seconds, Web API pushes callback to the Callback Queue
  5. Event Loop checks if the call stack is empty, then moves the callback from the queue and executes it

Output:

Start
End
Inside Timeout

Callback / Task Queue (Macrotask Queue)

The callback queue stores callbacks from asynchronous operations like timers, events, and network responses. It waits until the call stack is empty before pushing a callback back for execution. This is handled by the Event Loop.

Examples of macrotasks:

  • setTimeout
  • setInterval
  • I/O callbacks
  • DOM events

Event Loop

The Event Loop is the manager that constantly checks: “Is the call stack empty?”

If yes, it moves tasks (callbacks, promises, etc.) from their queues into the call stack for execution.

The Loop Process (Simplified)

while (true) {
  if (callStack.isEmpty()) {
    eventLoop.moveNextTaskFromQueueToStack();
  }
}

It’s like a traffic controller between the JavaScript engine (call stack), Web APIs, and queues (task and microtask).

Microtasks vs Macrotasks

JavaScript has two kinds of queues that the event loop manages differently.

Microtask Queue

  • Handles promises, MutationObservers, and queueMicrotask()
  • Higher priority than macrotasks
  • Executed immediately after the current call stack is empty, before any macrotasks
Promise.resolve().then(() => console.log("Microtask"));
queueMicrotask(() => console.log("Also Microtask"));

Macrotask Queue

  • Handles setTimeout, setInterval, fetch callbacks, DOM events, etc.
  • Executed after microtasks are done

Execution Order Example

console.log("1");

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

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

console.log("4");

Step-by-Step:

  1. console.log(“1”) executes
  2. setTimeout() goes to Web API, then callback to macrotask queue
  3. Promise.then() goes to microtask queue
  4. console.log(“4”) executes
  5. Call stack empty, Event loop checks microtask queue first and runs “3”
  6. Then macrotask queue runs “2”

Output:

1
4
3
2

Full Flow Diagram

        ┌────────────────────────────┐
        │        JavaScript Engine   │
        │ ┌──────────────┐           │
        │ │  Call Stack  │◄──────────┐│
        │ └──────────────┘           ││
        └──────────┬─────────────────┘│
                   │                  │
                   ▼                  │
         ┌──────────────────┐         │
         │    Web APIs      │         │
         └──────────────────┘         │
                   │                  │
                   ▼                  │
       ┌──────────────────────┐       │
       │  Callback Queue      │◄──────┘
       └──────────────────────┘


           ┌────────────────┐
           │   Event Loop   │
           └────────────────┘


           ┌────────────────┐
           │  Call Stack    │
           └────────────────┘

Example: fetch() and Event Loop

console.log("Start");

fetch("https://api.example.com")
  .then(() => console.log("Fetch done"));

console.log("End");

Execution:

  1. “Start” logged immediately
  2. fetch() is handled by Web API for the network request asynchronously
  3. “End” logged immediately
  4. After fetch completes, .then() callback goes to microtask queue
  5. Event Loop executes the .then() callback next

Output:

Start
End
Fetch done

Summary of All Components

ComponentManaged ByHandlesPriority
Call StackJS EngineRunning current function
HeapJS EngineMemory (objects, closures)
Web APIsBrowserAsync tasks (timers, DOM, fetch)
Microtask QueueEvent LoopPromises, MutationObserverHigh
Macrotask QueueEvent LoopsetTimeout, I/O, eventsLow
Event LoopBrowserCoordinates everything

Key Takeaways

The JavaScript engine executes one task at a time. The Web APIs handle async work like timers and network requests in the background. The Event Loop constantly checks the call stack. Once the stack is empty, it moves microtasks (like promises) first, then macrotasks (like timeouts) into execution.

This makes JavaScript non-blocking and asynchronous, even though it’s single-threaded.

Analogy

Think of:

  • Call Stack: The Chef (can cook one dish at a time)
  • Web APIs: Kitchen assistants (boil water, bake, fetch data)
  • Callback Queue: Completed orders waiting to be served
  • Event Loop: The Waiter, checking if the chef is free to serve next order
  • Microtasks: VIP orders (promises) that always get served first

Understanding the event loop and how JavaScript coordinates between synchronous execution and asynchronous operations is fundamental to building responsive web applications.