Manifest Stack home

What happens when you run a JS code

Part 1: The Two Phases

Every time JavaScript runs your code, it goes through exactly two phases inside the JS engine. Understanding these phases explains hoisting, the temporal dead zone, and many other behaviours that seem like magic.

Phase 1: Memory Creation (Compilation)

Before a single line of your code runs, the engine scans through the entire script. It allocates memory for all variables and functions it finds. Variables get undefined, function declarations get the whole function body stored.

Phase 2: Code Execution

Now the engine goes line by line from top to bottom and actually runs the code — assigning values, calling functions, evaluating expressions.

Part 2: Step by Step — What Actually Happens

Step 1: Parsing — code becomes tokens

The engine reads your raw text and breaks it into meaningful pieces called tokens: keywords (let, function), identifiers (variable names), operators, literals.

javascript
// Your source code:
let x = 10 + 5;

// Becomes tokens:
// [let] [x] [=] [10] [+] [5] [;]

Step 2: AST — tokens become a tree

The tokens are arranged into an Abstract Syntax Tree (AST) — a tree structure that represents the meaning of your code. Tools like linters, Babel, and TypeScript all work by reading the AST.

javascript
// let x = 10 + 5  becomes roughly:
//
// VariableDeclaration
//   └── VariableDeclarator
//         ├── Identifier (x)
//         └── BinaryExpression (+)
//               ├── Literal (10)
//               └── Literal (5)

Step 3: Compilation — AST becomes bytecode

Modern JS engines (like V8) don't just interpret the AST line by line — they compile it to bytecode first, then use a JIT (Just-In-Time) compiler to turn hot code paths into native machine code for maximum speed.

javascript
// You write:
function add(a, b) { return a + b; }
for (let i = 0; i < 1000000; i++) add(i, 1);

// After many calls, V8 notices add() is "hot"
// → JIT compiles it directly to machine code
// → runs at near-native speed

Step 4: Execution Context is created

Every time code runs, an Execution Context is created. There is always one Global Execution Context for the whole script, and a new one is created each time a function is called. Each context has two things: a Variable Environment (where variables live) and a reference to the outer scope.

javascript
// Global Execution Context created first:
let name = "Arjun";

function greet() {
    // New Execution Context created when greet() is called:
    let msg = "Hello, " + name; // can access outer 'name'
    console.log(msg);
}

greet(); // Hello, Arjun

Step 5: Memory phase — hoisting happens here

javascript
// Before execution, memory phase scans the code:

console.log(x); // undefined (not an error!)
var x = 10;     // ← x was found in memory phase, set to undefined

greet();        // ✅ works! function stored in memory phase
function greet() { console.log("Hi!"); }

console.log(y); // ❌ ReferenceError
let y = 5;      // let is in TDZ until this line

Part 3: The Full Picture

javascript
// Here's what happens when you run this script:

var a = 1;          // Memory phase: a = undefined
                    // Execution phase: a = 1

function sayHi() {
    var b = 2;      // New execution context when called
    console.log(a + b);
}

sayHi();            // → creates context → b=2 → logs 3
                    // → context destroyed after return

// Summary:
// 1. Parse → tokens → AST
// 2. Compile → bytecode (+ JIT for hot code)
// 3. Global execution context created
// 4. Memory phase: hoist vars (undefined) and functions
// 5. Execution phase: run line by line