JavaScript Execution Model and Asynchronous Processing

Understanding single-threaded execution and the event loop

By Hank Kim

JavaScript Execution Model and Asynchronous Processing

Understanding single-threaded execution and the event loop

JavaScript Execution Model and Asynchronous Processing

In this document, I want to explain how JavaScript handles asynchronous tasks despite being a single-threaded language, how the runtime environment supports non-blocking operations, and the role of the event loop. I will also briefly cover Web Workers as separate sub-threads.

High-level view of JS runtime: call stack, Web APIs, task/microtask queues, and event loop Figure: The JavaScript runtime model enabling non-blocking async execution.

1. JavaScript as a Single-Threaded Language

  • The JavaScript engine (e.g., V8) has one call stack.
  • Only one operation executes at a time.
  • If a long-running task blocks the call stack, the UI freezes and no other functions can run.

This is why blocking operations (network requests, file I/O, database queries) are problematic in JavaScript.


2. Asynchronous Task Execution

JavaScript itself cannot handle I/O asynchronously. Instead, the runtime environment provides background systems to offload work:

  • Browser: Web APIs (fetch, setTimeout, DOM events, etc.)
  • Node.js: libuv (C++ library that manages I/O and thread pool)

Flow:

  1. Main thread encounters an asynchronous function.
  2. Task is delegated to the background system (Web API or libuv).
  3. Once complete, the background system pushes a callback into a queue.
  4. The event loop moves callbacks into the call stack when it is empty.

Network request leaves the call stack and is handled by Web APIs while main thread continues Figure: Offloading async work to the environment keeps the call stack free.


3. The Event Loop Model

  • Call Stack: Executes JavaScript functions in LIFO order.
  • Heap: Stores objects and variables.
  • Task Queue / Microtask Queue: Holds completed async callbacks.
  • Event Loop:

    • Monitors if the call stack is empty.
    • If empty, dequeues a callback and pushes it onto the stack.

Callback queued by Web API and moved to call stack when empty Figure: Event loop transferring a completed task from the queue to the stack.

This is the core mechanism that enables non-blocking asynchronous execution.


4. Web Worker

  • A Web Worker is a separate JavaScript runtime created explicitly by the developer with new Worker().
  • It has its own call stack and heap, running on a sub-thread independent of the main thread.
  • Communication with the main thread happens only through message passing (postMessage / onmessage).
  • Unlike Web APIs or libuv, Web Workers are not I/O managers—they are used for CPU-intensive tasks (e.g., image processing, data parsing) that would otherwise block the UI.

5. Key Points

  • JavaScript engines are single-threaded.
  • Asynchronous behavior comes from the runtime environment (Web APIs in browsers, libuv in Node.js).
  • The event loop coordinates when async callbacks return to the main thread.
  • Web Workers provide a way to run heavy computations on a separate thread, but they do not handle I/O operations.

Tags: JavaScript