How JavaScript Runs: Inside the Engine

A deep technical dive into the JavaScript Runtime Environment. Understanding Execution Contexts, the Call Stack, the Event Loop (Microtasks vs Macrotasks), and V8 compilation.

14 min read31 Jan 2026
How JavaScript Runs: Inside the Engine

JavaScript is a synchronous, single-threaded language. This means it has one Call Stack and can perform only one operation at a time. However, it powers complex, non-blocking applications.

How does it achieve this? By delegating asynchronous tasks to the Runtime Environment.

To understand how JavaScript works meaningfuly, we must dissect the four core pillars of the runtime:

  1. The Memory Heap: Memory allocation.
  2. The Call Stack: Execution order.
  3. Web APIs / Node APIs: Asynchronous capabilities.
  4. The Event Loop: Coordination between the stack and the queues.

Event Loop Visualization


1. Execution Context (The Core Mechanism)

Before any code executes, the JavaScript engine creates a global Execution Context. Every time a function is invoked, a new Function Execution Context is created.

Every execution context undergoes two distinct phases:

Phase 1: Memory Creation (The "Hoisting" Phase)

In this pass, the engine scans the code without executing it. It allocates memory for variables and functions.

  1. Variables (var): Allocated memory and initialized to undefined.
  2. Variables (let, const): Allocated memory but uninitialized (Temporal Dead Zone).
  3. Functions: The entire function references are stored in memory.

Phase 2: Execution

The engine runs through the code line-by-line, assigning values and executing commands.

Example: "Hoisting" in Action

console.log(x); // undefined (allocated in Phase 1)
console.log(multiply); // [Function: multiply]
// console.log(y); // ReferenceError (in Temporal Dead Zone)

var x = 10;
let y = 20;

function multiply(a, b) {
  return a * b;
}

Step-by-Step Execution:

  1. Line 1: x exists in memory (from Phase 1), value is undefined. Log: undefined.
  2. Line 2: multiply function matches the memory reference. Log: [Function].
  3. Line 5: Variable x is assigned 10.
  4. Line 6: Variable y is initialized and assigned 20.

2. The Call Stack (LIFO)

The Call Stack tracks where we are in the program. It uses a Last-In, First-Out (LIFO) data structure.

When a function executes, a Stack Frame is created containing the function's Execution Context (arguments, local variables).

Logic Flow

function one() {
  two();
}

function two() {
  console.log('Two');
}

one();

Stack Operations:

  1. Push Main(): The global context starts.
  2. Push one(): one is called.
  3. Push two(): Inside one, two is called. It sits on top of one.
  4. Execute two: Logs "Two".
  5. Pop two(): Function two finishes.
  6. Pop one(): Function one resumes and finishes.
  7. Pop Main(): Program ends.

If the Stack exceeds its size limit (e.g., infinite recursion), you get a Stack Overflow.


3. Asynchrony & The Event Loop

If JS is single-threaded, how does setTimeout work without blocking the code?

The Secret: setTimeout is not part of the JS Engine. It is a Web API provided by the browser (or C++ API in Node.js).

The Architecture

  1. Call Stack: Executes JS code.
  2. Web APIs: Handles timers, AJAX, DOM events (in background threads).
  3. Callback Queue (Macrotasks): Holds callbacks from setTimeout, setInterval.
  4. Microtask Queue: Holds callbacks from Promises, queueMicrotask. Higher Priority.
  5. Event Loop: The warden. Use this simple algorithm:

Algorithm:

  1. Is the Call Stack empty?
  2. If NO: Wait.
  3. If YES:
    • Check Microtask Queue. Run ALL microtasks until empty.
    • Then, take ONE item from Callback Queue.
    • Push it to Call Stack.

Code Example: Priority in Action

console.log('1. Start');

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

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

console.log('4. End');

Execution Order Analysis:

  1. console.log('1. Start') pushes to Stack -> Logs -> Pops.
  2. setTimeout pushes to Stack. Engine delegates to Web API. Web API finishes instantly (0ms) and pushes callback to Callback Queue. Stack Pops.
  3. Promise pushes to Stack. Resolution adds callback to Microtask Queue. Stack Pops.
  4. console.log('4. End') pushes to Stack -> Logs -> Pops.
  5. Call Stack is Empty. Event Loop wakes up.
  6. Check Microtasks: Finds Promise callback. Pushes to Stack -> Logs '3. Promise' -> Pops.
  7. Check Macrotasks: Finds Timeout callback. Pushes to Stack -> Logs '2. Timeout' -> Pops.

Final Output:

1. Start
4. End
3. Promise
2. Timeout

4. Under the Hood: V8 Engine (Ignition & TurboFan)

The V8 Engine (used in Chrome & Node.js) compiles JavaScript in real-time.

  1. Parsing: Source code converted to AST (Abstract Syntax Tree).
  2. Ignition (Interpreter): Converts AST to Bytecode and executes it. This is fast start-up, but slower execution.
  3. TurboFan (Compiler): Runs in the background. It detects "hot" code (code run frequently) and compiles it to optimized Machine Code.
    • Optimization Assumption: "This function always takes two integers."
    • De-optimization: If execution proves assumptions wrong (e.g., you suddenly pass a string), it discards machine code and reverts to Bytecode.

Performance Tip: Keep object shapes and variable types consistent to prevent TurboFan from constantly de-optimizing your code.