Skip to main content

Command Palette

Search for a command to run...

Your Last Min JS Revision - Part 1: The Runtime

Updated
6 min read
Your Last Min JS Revision - Part 1: The Runtime

Table of Contents

  1. How JS Goes From Source → Execution

  2. Single-Threaded Foundation

  3. Execution Context & The Two-Phase Model

  4. The Call Stack

  5. Hoisting & The Temporal Dead Zone

  6. The Host Environment — Where JS Stops

  7. Event Loop Mechanics

  8. Priority Model — Microtasks vs Macrotasks

  9. Async/Await — Continuations Under The Hood

  10. Summary — The Event Loop Decision Model


1. How JS Goes From Source → Execution

JavaScript is not purely interpreted nor purely compiled — it uses Just-In-Time (JIT) compilation. The pipeline varies slightly across engines (V8, SpiderMonkey, JavaScriptCore), but the general flow is:

Step-by-step:

  1. Parser — Converts source code into an AST (Abstract Syntax Tree). This is where syntax errors are caught.

  2. Interpreter — Walks the AST and produces bytecode. Execution starts immediately on bytecode.

  3. Profiler — Watches for "hot" (frequently executed) code paths during execution.

  4. JIT Compiler — Compiles hot bytecode paths into optimized machine code for faster execution.

  5. Deoptimization — If assumptions made by the JIT compiler break (e.g., a variable's type changes), the engine falls back to bytecode.

Key takeaway: Your source code is parsed, converted to bytecode, executed, and selectively optimized at runtime. This is why warm-up iterations matter for performance benchmarks.


2. Single-Threaded Foundation

JavaScript executes:

  • ONE instruction

  • at ONE time

  • on ONE call stack

console.log("A");
console.log("B");
console.log("C");

Output:

A
B
C

No parallelism. No concurrent execution. Code runs sequentially on the main thread. The illusion of concurrency comes from the event loop (covered in Section 7).


3. Execution Context & The Two-Phase Model

Every time code runs, the engine creates an Execution Context. Two types:

Context When created
Global Execution Context (GEC) When your script starts
Function Execution Context (FEC) When a function is called

Each context has two compartments:

  • Memory Component (VariableEnvironment) — stores variables and function declarations

  • Code Component (LexicalEnvironment) — execution happens line by line

The Two Phases

1: Memory Creation
2: Code Execution
var x = 10;
function greet() {
  console.log("Hi");
}

After Phase 1 (Memory Creation):

Identifier Value
x undefined
greet Function body (hoisted in full)

Phase 2 (Code Execution):

var x = 10;     // x: undefined → 10
console.log(x);  // prints 10
greet();         // creates a new FEC, pushes to call stack

4. The Call Stack

The call stack is a LIFO (Last In, First Out) data structure that tracks which function is executing now.

function one() {
  two();
}
function two() {
  console.log("Inside two");
}
one();

Stack state at each step:

Why this matters

Because there is only one call stack:

while (true) {}

The browser freezes. The call stack never empties, so the event loop cannot process anything — no rendering, no callbacks, no user input.


5. Hoisting & The Temporal Dead Zone

During Phase 1 (Memory Creation), variable and function declarations are moved to the top of their scope. This is "hoisting".

var hoisting

console.log(x); // undefined
var x = 5;

No error. x was allocated and initialized to undefined during Phase 1.

Function hoisting

greet(); // works
function greet() {
  console.log("Hi");
}

The entire function body is hoisted.

let / const hoisting — The Temporal Dead Zone (TDZ)

console.log(a);
let a = 20;

Output: ReferenceError

a does exist in memory — it was allocated during Phase 1. But unlike var, it was not initialized. The gap between allocation and initialization is the Temporal Dead Zone (TDZ).

Comparison table

Keyword Allocated in Phase 1? Initialized? Default value
var undefined
let ❌ (TDZ)
const ❌ (TDZ)

Hidden trap: function expression vs declaration

sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {};
sayHi(); // works
function sayHi() {}

Why the difference?

  • function sayHi() {} — full function body hoisted during Phase 1

  • var sayHi = function() {}sayHi is hoisted as undefined (it's a var). The assignment = function() {} happens during Phase 2. Calling undefined() produces a TypeError.


6. The Host Environment — Where JS Stops

JavaScript the language does not handle:

  • Timers (setTimeout, setInterval)

  • DOM events

  • Network requests (fetch, XMLHttpRequest)

  • console I/O

These are provided by the host environment — a browser, Node.js, etc.

The engine only executes code and manages the stack. Everything asynchronous is delegated to the host, which pushes callbacks into queues when ready.


7. Event Loop Mechanics

setTimeout — minimum delay, not guaranteed time

console.log("Start");

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

console.log("End");

Output:

Start
End
Timeout

setTimeout(fn, 0) means minimum 0ms before the callback is queued, not "execute instantly". The callback waits in the macrotask queue until the call stack is empty.


8. Priority Model — Microtasks vs Macrotasks

console.log("Start");

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

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

console.log("End");

Output:

Start
End
Promise
Timeout

Two queues, different priority

Queue Contains Priority
Microtask Queue Promise callbacks, queueMicrotask, MutationObserver Higher
Macrotask Queue setTimeout, setInterval, DOM events, I/O callbacks Lower

The processing rule

After every synchronous operation:

  1. Run ALL microtasks (drain the microtask queue completely)

  2. Run ONE macrotask (dequeue and execute one)

  3. Repeat

Real-world warning: microtask starvation

function loop() {
  Promise.resolve().then(loop);
}
loop();

This freezes the browser. Each microtask queues another microtask, so the microtask queue never drains. The event loop never reaches macrotasks or rendering.


9. Async/Await — Continuations Under The Hood

async function test() {
  console.log(1);
  await Promise.resolve();
  console.log(2);
}

test();
console.log(3);

Output:

1
3
2

await suspends the function. Everything after await is wrapped into a microtask continuation.

Conceptually equivalent to:

function test() {
  console.log(1);
  return Promise.resolve().then(() => {
    console.log(2);
  });
}

10. Summary — The Event Loop Decision Model

Final verification

setTimeout(() => console.log("A"));
Promise.resolve().then(() => console.log("B"));
console.log("C");

Output:

C
B
A

Why:

  1. console.log("C") — synchronous, executes immediately

  2. console.log("B") — microtask (Promise then), runs after sync code completes

  3. console.log("A") — macrotask (setTimeout), runs last after microtasks are drained


One thread, two queues, one loop. No magic — just a determined execution model.

Javascript

Part 1 of 1