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.
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:
- The Memory Heap: Memory allocation.
- The Call Stack: Execution order.
- Web APIs / Node APIs: Asynchronous capabilities.
- The Event Loop: Coordination between the stack and the queues.

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.
- Variables (
var): Allocated memory and initialized toundefined. - Variables (
let,const): Allocated memory but uninitialized (Temporal Dead Zone). - 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:
- Line 1:
xexists in memory (from Phase 1), value isundefined. Log:undefined. - Line 2:
multiplyfunction matches the memory reference. Log:[Function]. - Line 5: Variable
xis assigned10. - Line 6: Variable
yis initialized and assigned20.
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:
- Push
Main(): The global context starts. - Push
one():oneis called. - Push
two(): Insideone,twois called. It sits on top ofone. - Execute
two: Logs "Two". - Pop
two(): Functiontwofinishes. - Pop
one(): Functiononeresumes and finishes. - 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
- Call Stack: Executes JS code.
- Web APIs: Handles timers, AJAX, DOM events (in background threads).
- Callback Queue (Macrotasks): Holds callbacks from
setTimeout,setInterval. - Microtask Queue: Holds callbacks from
Promises,queueMicrotask. Higher Priority. - Event Loop: The warden. Use this simple algorithm:
Algorithm:
- Is the Call Stack empty?
- If NO: Wait.
- 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:
console.log('1. Start')pushes to Stack -> Logs -> Pops.setTimeoutpushes to Stack. Engine delegates to Web API. Web API finishes instantly (0ms) and pushes callback to Callback Queue. Stack Pops.Promisepushes to Stack. Resolution adds callback to Microtask Queue. Stack Pops.console.log('4. End')pushes to Stack -> Logs -> Pops.- Call Stack is Empty. Event Loop wakes up.
- Check Microtasks: Finds Promise callback. Pushes to Stack -> Logs
'3. Promise'-> Pops. - 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.
- Parsing: Source code converted to AST (Abstract Syntax Tree).
- Ignition (Interpreter): Converts AST to Bytecode and executes it. This is fast start-up, but slower execution.
- 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.