Headlines

Deconstructing Code & Concepts

I'm Shaquille, a Software Engineer bringing 3+ years of experience. Here, I offer my insights and perspectives on various coding topics.

JavaScript Behind the Scenes

JavaScript Behind the Scenes

Published on June 20, 2025

JavaScriptCoreAdvanced

The Engine and Runtime Environment

A JavaScript engine, like Chrome's V8 or Firefox's SpiderMonkey, lives at the core of JavaScript execution. The browser provides a the runtime environment, offering essential functionalities beyond the engine itself. These functionalities become accessible through the window object.

The engine organizes code using two key components:

  • Call Stack: This component manages the order of JavaScript's synchronous code execution by holding execution contexts.
  • Heap: This area handles memory allocation for objects. A garbage collector continuously reclaims memory from objects no longer in use. Objects persist in the heap as long as a reference to them exists within the global execution call stack.

The Call Stack

When a JavaScript script first loads, the engine places the Global Execution Context at its base. This initial context sets up the global object (e.g., window in browsers, global in Node.js) and sets the this keyword binding, also managing global declarations.

As functions are called, a new execution context (often called a "frame") is pushed onto the top of the stack. The Call Stack operates on a Last-In, First-Out (LIFO) principle. Each frame holds the function's local variables, arguments, and the point where execution should resume after the function returns.

The engine processes functions by executing the frame currently at the top of the stack. When a function completes its execution, its frame is popped off the stack, and control returns to the frame below. An empty Call Stack marks the completion of all synchronous code execution. However, variables and functions defined in the global context persist in memory and remain accessible, allowing the program to respond to future asynchronous events via the Event Loop.

Web APIs

Browsers extend JavaScript's capabilities through Web APIs. These APIs offer access to browser features that the core JavaScript engine does not include. Examples include:

  • DOM (Document Object Model)
  • Timers
  • Fetch API

These APIs allow JavaScript to interact with the web environment effectively.

Runtimes Beyond the Browser

JavaScript engines operate in various environments, each providing its unique set of functionalities. The Web APIs discussed above are specific to browser environments. A Node.js runtime environment operates server-side and does not include browser-specific APIs. You will not find direct access to the document or window objects in Node.js.

Instead, Node.js provides its own powerful set of APIs tailored for server-side and system-level programming. These include:

  • The fs module for file system operations (e.g., reading and writing files).
  • The http and https modules for building web servers and making network requests.
  • The path module for handling file and directory paths.
  • The process object for interacting with the Node.js process itself (e.g., environment variables, command-line arguments).

Despite these differences in available APIs, Node.js also leverages the V8 JavaScript engine and utilizes the Event Loop for its non-blocking, asynchronous concurrency model, similar to browsers.

The Event Loop: Concurrency and Priorities

JavaScript operates on a single thread, meaning it processes one task at a time. The Event Loop is a manages all asynchronous operations, preventing blocking behavior and ensuring JavaScript's single thread is efficient. It continuously monitors the Call Stack, the Microtask Queue, and the Macrotask Queue (also known as the Callback Queue).

The Event Loop adheres to a strict order for handling operations:

  1. Synchronous Execution (Call Stack): Functions placed on the global call stack execute first.
  2. Microtask Queue Processing: Once the Call Stack completely clears (meaning all synchronous code has finished), the Event Loop processes any waiting microtasks. Microtasks originate from operations like promise callbacks (e.g., .then(), .catch(), .finally()) or functions queued with queueMicrotask. The Event Loop empties the Microtask Queue before moving to the next stage. This prioritization ensures responsive execution for promises and other critical operations.
  3. Macrotask Queue Processing: Only after all microtasks complete does the Event Loop take a single task from the Macrotask Queue (Callback Queue) and push it onto the Call Stack for execution. Macrotasks include operations like setTimeout, setInterval, network requests (initiated by Fetch API), and DOM events (e.g., an onClick callback).

When an asynchronous operation finishes, its associated callback function moves to the appropriate queue (either Microtask or Macrotask). This continuous monitoring and strict prioritization ensure smooth, non-blocking concurrency, allowing JavaScript to remain responsive while waiting for long-running operations.

Consider the following example to visualize this order:

console.log("1. Synchronous code: Start");
// This executes immediately, going onto the Call Stack.

setTimeout(() => {
  console.log("3. Macrotask: setTimeout with 0ms");
  // This callback goes into the Macrotask Queue.
  // Even with 0ms, it waits for the Microtask Queue to clear.
}, 0);

Promise.resolve().then(() => {
  console.log("2. Microtask: Promise resolved");
  // This callback goes into the Microtask Queue.
  // The Event Loop prioritizes this queue after the Call Stack.
});

console.log("1. Synchronous code: End");
// This also executes immediately, on the Call Stack.

Conclusion

JavaScript executes code using an engine with a Heap for object memory and a Call Stack for synchronous execution. The Event Loop orchestrates asynchronous operations, ensuring non-blocking behavior by strictly prioritizing microtasks over macrotasks. Different runtimes, like browsers and Node.js, extend JavaScript with specific APIs. Understanding these core mechanisms empowers better, more efficient, and predictable JavaScript development.