Source text → tokens → AST → bytecode → optimised machine code. One call stack, one heap, and an event loop that feeds it work.
The pipeline inside V8
Source code → Parser → AST → Ignition (bytecode) → TurboFan (optimised machine code)
Parse
The source is tokenised and turned into an Abstract Syntax Tree. Syntax errors are found here — before a single line runs.
Interpret
The Ignition interpreter compiles the AST to compact bytecode and starts running it immediately (fast startup).
Profile
While running, the engine records which functions are hot and which types they see.
Optimise (JIT)
Hot functions are compiled by TurboFan (and the mid-tier Maglev) into machine code specialised for the observed types.
Deoptimise
If an assumption breaks (a function that always saw numbers suddenly gets a string), the engine throws the optimised code away and falls back to bytecode.
Memory: stack and heap
Execution contexts
Every time code runs it gets an execution context. There is one global context, plus one new context per function call. Each context is created in two phases:
| Phase | What happens |
|---|---|
| Creation (memory) | Scope is set up; var names are created as undefined, function declarations are stored in full, let/const/class names are created but uninitialised (TDZ). this is determined. |
| Execution | Code runs line by line; assignments fill in the values. |
The call stack in action
function third() {
console.log("3. in third — stack: global → first → second → third");
}
function second() {
console.log("2. in second");
third();
console.log("4. back in second");
}
function first() {
console.log("1. in first");
second();
console.log("5. back in first");
}
first();1. in first
2. in second
3. in third — stack: global → first → second → third
4. back in second
5. back in firstIf functions call each other without end, the stack overflows:
function forever(n) {
return forever(n + 1);
}
try {
forever(0);
} catch (e) {
console.log(e.name + ": " + e.message);
}RangeError: Maximum call stack size exceededSingle-threaded, yet non-blocking
JavaScript runs your code on one thread. Slow work (network, disk, timers) is handed to the runtime; when it finishes, a callback is queued and the event loop pushes it onto the stack once the stack is empty. See The Event Loop topic for the full picture.
Call stack → Runtime APIs (timers, I/O) → Task queues → Event loop → back to stack
Is JavaScript interpreted or compiled?
What is an execution context?