JavaScript Under the Hood: The V8 Engine, Execution Context, and Memory Architecture
Demystifying runtime internals for senior engineers: how the V8 engine parses ASTs, compiles hot code via TurboFan, and manages system memory from Call Stack frames to the dynamic Heap.
Elgun Mammadli
Front End Developer

Most developers start their JavaScript journey with console.log("Hello World"), variable declarations, or simple functions. However, mastering the language at a senior engineering level demands looking past the surface syntax: What does our code actually mean to the machine, and how does the engine execute it on the physical hardware?
In this deep dive, we explore JavaScript's internal mechanics: the V8 engine optimization pipeline, the lifecycle of an Execution Context, and the low-level reality of memory management.
1. Engine Internals: From Source Code to Machine Instructions
JavaScript is no longer a purely interpreted language; modern implementations leverage JIT (Just-In-Time) Compilation. The most prominent runtime engine is Google's V8, which powers Google Chrome, Node.js, and Deno.
When source code enters the engine, it navigates the following multi-stage pipeline:
Parser and the AST (Abstract Syntax Tree)
The engine begins with lexical analysis: scanning the raw characters into discrete tokens. Next, the parser converts these tokens into an AST—a hierarchical, tree-structured syntactic representation of the program.
Ignition (The Interpreter)
V8's interpreter, Ignition, consumes the AST and translates it into compact, platform-independent Bytecode. Bytecode runs immediately without waiting for heavy compilation steps, delivering instant startup times.
TurboFan and the Profiler (JIT Optimization)
As the bytecode executes, an internal Profiler monitors runtime behavior to detect frequently invoked functions—labeled as "Hot Code". It also collects runtime Type Feedback.
When a function is consistently invoked with stable types, V8's optimizing compiler, TurboFan, translates that bytecode directly into high-performance Machine Code tailored for the host CPU.
The Deoptimization Trap
JavaScript is dynamically typed. If TurboFan aggressively optimizes a function for numbers (number), and the code suddenly feeds it a string (string):
The engine discards the optimized machine instructions (a bailout) and drops back to slower bytecode interpretation. Writing monomorphic code (retaining consistent object shapes and variable types) is paramount for performance.
2. What Is an Execution Context?
From the moment code executes, JavaScript evaluates everything inside dedicated environments called Execution Contexts (EC). Simply put, an Execution Context is the conceptual wrapper containing the code currently being evaluated.
There are two primary variants:
- Global Execution Context (GEC): Instantiated automatically when the script loads. Only one GEC exists, establishing the global object (
windowin browsers,globalin Node.js). - Function Execution Context (FEC): Created on demand whenever a function is invoked.
Every execution context progresses through two distinct phases:
Phase 1: Creation Phase
Before executing a single line of code, the engine scans the scope and allocates memory:
- The global object (
windoworglobal) is bound. - The
thisreference is resolved for the current scope. - The Lexical Environment and Variable Environment structures are established.
- Memory space is reserved for identifiers and functions (this is the physical foundation of Hoisting).
Phase 2: Execution Phase
The engine steps sequentially through the code, binds concrete values to variables, and executes function invocations.
3. Hoisting and the Temporal Dead Zone (TDZ)
Hoisting is not physical code movement; it is an artifact of the Creation Phase allocating variable slots before runtime execution begins.
The distinction lies in how different declarations are initialized during this step:
- The
varkeyword: Memory is allocated during the creation phase and immediately initialized toundefined. Accessing it prior to assignment returnsundefinedwithout throwing an error. - The
letandconstkeywords: Memory is registered during the creation phase, but the identifier remains uninitialized. The window between entry into scope and the physical declaration statement is the Temporal Dead Zone (TDZ). Accessing an uninitialized identifier triggers aReferenceError.
4. The Call Stack and Memory Architecture: Stack vs. Heap
JavaScript is a single-threaded runtime with a single sequential call stack. Execution flow is managed by the Call Stack, while non-primitive data storage is coordinated within the Memory Heap.
| Property | Call Stack | Memory Heap |
|---|---|---|
| Role | Execution flow, primitives, and memory references (pointers) | Complex objects, arrays, and functions |
| Structure | Strict LIFO (Last In, First Out) ordering | Unordered, dynamically allocated memory space |
| Access Speed | Extremely fast direct memory access | Marginally slower due to pointer dereferencing |
The stack holds only the memory address pointer (0x0012FA) referencing the heap. When an object is assigned to another variable, only this pointer is copied, not the underlying object data:
The Call Stack and Stack Overflow
Each function invocation pushes a new Stack Frame onto the Call Stack. Once execution completes, the frame is popped. If recursive calls occur without an exit condition, stack memory is exhausted, throwing Maximum call stack size exceeded.
5. Garbage Collection: Mark-and-Sweep
Dynamically allocated heap memory cannot persist indefinitely. The engine's Garbage Collector (GC) periodically identifies and reclaims unreferenced memory.
Modern engines rely on the Mark-and-Sweep algorithm:
- Roots: Global variables and local variables in currently active stack frames form the baseline roots.
- Mark: The GC traverses references beginning from the roots, tagging all encountered objects as "reachable". It then follows outbound references recursively.
- Sweep: Any object on the heap lacking an active reference path from the root set is marked unreachable, and its allocated memory is reclaimed.
The Threat of Memory Leaks
When an object is no longer functional but remains anchored by an uncleaned event listener (addEventListener), an unintended global identifier, or an orphaned closure, the GC cannot sweep it. Over time, memory consumption climbs, introducing latency and browser tab crashes.
Share this article
JavaScript Under the Hood: The V8 Engine, Execution Context, and Memory Architecture