**Who is this for?** - 🔵 **Experienced devs** filling the "I know it works, but not *why*" gap - 🟢 **Ambitious freshers** who want to stand out in interviews - Anyone who has ever been confused by `this`, hoisting, closures, or async timingMost developers use JavaScript. Few understand what the engine actually does when your code runs. This series covers the internals — what no tutorial teaches but every senior dev is expected to know.
Recommended order: Follow the sections top to bottom — each one builds on the previous.
- The JavaScript Engine
- Memory — How JS Stores Data
- Execution Context
- The Global Execution Context
- Function Execution Context
- The Call Stack
- Scope & Scope Chain
- Hoisting — In Depth
- The
thisKeyword — Context Rules - Closures — How & Why
- The Event Loop & Concurrency Model
- Garbage Collection
- Memory Leaks — Causes & Fixes
- V8 Engine Internals — JIT & Optimization
The starting point. Before any code runs, you need to understand what is actually executing it.
JavaScript is not interpreted line by line the naive way. Engines like V8 (Chrome, Node), SpiderMonkey (Firefox), and JavaScriptCore (Safari) do a lot of work before your first variable is set.
Source Code
↓
Tokenizer / Lexer → breaks code into tokens
↓
Parser → builds an Abstract Syntax Tree (AST)
↓
Interpreter (Ignition) → produces bytecode, starts executing
↓
Profiler → watches for "hot" code (run many times)
↓
JIT Compiler (TurboFan)→ compiles hot paths to machine code
↓
Optimized Machine Code → runs at near-native speed
| # | Topic | Format | Notes |
|---|---|---|---|
| 1.1 | What is a JavaScript engine? | 📹 Long | V8, SpiderMonkey, JSCore overview |
| 1.2 | Lexing & Tokenization | 📹 Long | How source code becomes tokens |
| 1.3 | Abstract Syntax Tree (AST) | 📹 Long | Use astexplorer.net live demo |
| 1.4 | Interpreter vs Compiler | 📹 Long | Why JS is "both" |
| 1.5 | JIT Compilation basics | ⚡ Short | Hot functions, deoptimization |
Example Short: Your JS code isn't interpreted — here's what actually happens ⚡
Example Long Video: Inside V8 — from source code to machine code, step by step
Where do variables actually live? Why does
let x = {}behave differently fromlet x = 5?
JavaScript has two memory regions. Understanding them explains reference bugs, shallow copy issues, and performance traps.
┌─────────────────────────────────┐
│ STACK │
│ ───────────────────────────── │
│ Primitives stored by VALUE │
│ Fixed size, fast access │
│ let x = 5 → [5] on stack │
│ let y = x → [5] copy on stack│
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ HEAP │
│ ───────────────────────────── │
│ Objects/Arrays stored by REF │
│ Dynamic size, slower │
│ let obj = {} → ref on stack │
│ data on heap │
│ let b = obj → same ref! │
└─────────────────────────────────┘
// Primitives — copied by value
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 — untouched
// Objects — copied by reference
let obj1 = { name: "Rahul" };
let obj2 = obj1;
obj2.name = "Priya";
console.log(obj1.name); // "Priya" — same reference!| # | Topic | Format | Notes |
|---|---|---|---|
| 2.1 | Stack vs Heap memory | 📹 Long | Visual walkthrough |
| 2.2 | Primitive types — stored by value | 📹 Long | number, string, boolean, null, undefined, symbol, bigint |
| 2.3 | Reference types — stored by reference | 📹 Long | objects, arrays, functions |
| 2.4 | Shallow copy vs Deep copy | 📹 Long | {...obj}, Object.assign, structuredClone |
| 2.5 | typeof and memory implications |
⚡ Short | typeof null === "object" explained |
| 2.6 | Memory allocation at runtime | 📹 Long | How the engine allocates per declaration |
Example Short: Why obj2 = obj1 doesn't copy anything — reference trap explained 🎯
Example Long Video: Stack vs Heap — the memory model every JS dev must visualize
Project: Debug a broken deep-clone function — find the reference bugs live
The single most important concept to understand if you want to master JavaScript.
An Execution Context (EC) is the environment in which JavaScript code is evaluated and executed. Every time code runs, an EC is created.
┌──────────────────────────────────────────┐
│ EXECUTION CONTEXT TYPES │
├──────────────────────────────────────────┤
│ 1. Global Execution Context (GEC) │
│ → Created once when the script loads │
│ → Creates: global object + `this` │
│ │
│ 2. Function Execution Context (FEC) │
│ → Created every time a function runs │
│ → Has its own Variable Environment │
│ │
│ 3. Eval Execution Context │
│ → Created inside eval() — avoid it │
└──────────────────────────────────────────┘
Phase 1: CREATION (Memory Phase)
─────────────────────────────────
→ Scan for variables → assign undefined
→ Scan for functions → store full definition
→ Set up scope chain
→ Determine value of `this`
Phase 2: EXECUTION (Code Phase)
─────────────────────────────────
→ Run code line by line
→ Assign actual values to variables
→ Execute function calls (creates new EC)
| # | Topic | Format | Notes |
|---|---|---|---|
| 3.1 | What is an Execution Context? | 📹 Long | The core mental model |
| 3.2 | Creation Phase vs Execution Phase | 📹 Long | Memory allocation, then running |
| 3.3 | Variable Environment | 📹 Long | Where identifiers live inside an EC |
| 3.4 | Lexical Environment | 📹 Long | EC + reference to outer EC |
| 3.5 | The three types of EC | ⚡ Short | GEC, FEC, Eval |
Example Long Video: Execution Context — the engine's blueprint for running your code
The EC that exists before your first line of code runs.
Global Execution Context
├── Global Object → window (browser) / global (Node)
├── this → points to the global object
├── Outer Environment → null (nothing above global)
└── Variable Environment → all var declarations + function defs
var name = "Rahul";
var age = 25;
function greet() {
console.log("Hello", name);
}
greet();CREATION PHASE (what engine does first):
─────────────────────────────────────────
name → undefined
age → undefined
greet → function greet() { ... } ← full definition stored
EXECUTION PHASE (running line by line):
─────────────────────────────────────────
name = "Rahul"
age = 25
greet() → creates a new Function Execution Context
| # | Topic | Format | Notes |
|---|---|---|---|
| 4.1 | Global EC creation | 📹 Long | window/global object, this |
| 4.2 | Global object in browser vs Node | ⚡ Short | window vs global vs globalThis |
| 4.3 | How var declarations live on the global object |
📹 Long | var x = 1 → window.x |
| 4.4 | Why let/const don't attach to global object |
📹 Long | Block scope in GEC |
| 4.5 | Live walkthrough — creation + execution phase | 📹 Long | Step-by-step with diagrams |
Example Short: Why window.x works but window.y doesn't — var vs let in global scope 🌍
Example Long Video: Global Execution Context — what runs before your code does
Every function call creates its own universe. Here's what's inside it.
Function Execution Context (created on every call)
├── Variable Environment → local vars, arguments object
├── arguments object → array-like list of passed args
├── this → depends on HOW the function is called
├── Outer Environment Ref → points to where function was DEFINED
└── Scope Chain → chain of outer environments
var x = 1;
function outer() {
var y = 2;
function inner() {
var z = 3;
console.log(x, y, z); // 1, 2, 3
}
inner();
}
outer();Call Stack state:
─────────────────
[inner EC] ← currently running
[outer EC] ← paused, waiting
[Global EC] ← paused, waiting
inner EC:
z = 3
outer ref → outer EC
outer EC ref → Global EC ← scope chain!
| # | Topic | Format | Notes |
|---|---|---|---|
| 5.1 | Function EC creation | 📹 Long | What is created on every call |
| 5.2 | The arguments object |
📹 Long | vs rest parameters |
| 5.3 | this inside functions |
📹 Long | Strict vs non-strict mode |
| 5.4 | Lexical environment & outer reference | 📹 Long | Foundation of closures |
| 5.5 | Arrow functions — no own EC for this |
📹 Long | Why arrow functions inherit this |
| 5.6 | Function EC lifecycle | ⚡ Short | Created → runs → destroyed |
Example Short: Arrow functions don't have their own \this` — here's exactly why 🎯 **Example Long Video:**Function Execution Context — full walkthrough with nested calls **Project:**Trace any function's EC manually — build a visualizer tool`
The data structure that tracks where the engine is at any given moment.
The call stack is a LIFO (Last In, First Out) stack of execution contexts. It is the engine's way of knowing "what is currently running, and where do I go back when it finishes."
function a() { b(); }
function b() { c(); }
function c() { console.log("deep"); }
a();Step 1: [Global EC] ← a() called
Step 2: [a EC] [Global EC] ← b() called inside a
Step 3: [b EC] [a EC] [Global EC] ← c() called inside b
Step 4: [c EC] [b EC] [a EC] [GEC] ← console.log runs
Step 5: [b EC] [a EC] [Global EC] ← c EC popped
Step 6: [a EC] [Global EC] ← b EC popped
Step 7: [Global EC] ← a EC popped
Step 8: [] ← script done
function recurse() {
recurse(); // no base case
}
recurse(); // RangeError: Maximum call stack size exceeded| # | Topic | Format | Notes |
|---|---|---|---|
| 6.1 | What is the Call Stack? | 📹 Long | LIFO, push/pop, execution order |
| 6.2 | Stack frames — what's in each frame | 📹 Long | EC per frame |
| 6.3 | Stack overflow — cause & diagnosis | 📹 Long | Infinite recursion, Chrome DevTools |
| 6.4 | Reading the call stack in DevTools | 📹 Long | Practical debugging skill |
| 6.5 | Single-threaded nature of JS | ⚡ Short | One call stack = one thing at a time |
Example Short: Stack Overflow in JS — what it actually means and how to debug it 🔥
Example Long Video: The Call Stack — visualized from zero to stack overflow
Where can a variable be accessed from? The scope chain answers this.
┌─────────────────────────────────────────┐
│ GLOBAL SCOPE │
│ var / let / const at top level │
│ ┌───────────────────────────────────┐ │
│ │ FUNCTION SCOPE │ │
│ │ var declared inside a function │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ BLOCK SCOPE │ │ │
│ │ │ let / const inside {} │ │ │
│ │ └─────────────────────────────┘ │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
let global = "G";
function outer() {
let o = "O";
function inner() {
let i = "I";
console.log(i); // found in inner EC
console.log(o); // not in inner → look in outer → found
console.log(global); // not in inner/outer → look in GEC → found
}
inner();
}
outer();| # | Topic | Format | Notes |
|---|---|---|---|
| 7.1 | Global, Function, Block scope | 📹 Long | With var / let / const differences |
| 7.2 | Lexical scope — defined at write time | 📹 Long | Not at call time |
| 7.3 | Scope chain — how lookup works | 📹 Long | Walking outer references |
| 7.4 | Variable shadowing | ⚡ Short | Inner var hiding outer var |
| 7.5 | var leaking out of blocks |
⚡ Short | The classic var bug in loops |
| 7.6 | Temporal Dead Zone revisited | 📹 Long | let/const in creation phase |
Example Short: var in a for loop — the bug that's bitten every JS developer 🐛
Example Long Video: Scope Chain — how JS finds your variables, step by step
Variables and functions aren't where you put them. Here's where the engine puts them.
Hoisting is a direct result of the Creation Phase of the Execution Context. The engine scans the code before executing it.
// What you write:
console.log(x); // undefined — not an error
greet(); // "Hello" — works fine
sayBye(); // TypeError — not a function
var x = 5;
function greet() { console.log("Hello"); }
var sayBye = function() { console.log("Bye"); };
// What the engine sees after creation phase:
var x = undefined;
var sayBye = undefined;
function greet() { console.log("Hello"); }
// ... then execution beginsconsole.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 10;
// 'a' IS in memory (hoisted) but in the Temporal Dead Zone
// until the let declaration line is reached in execution| # | Topic | Format | Notes |
|---|---|---|---|
| 8.1 | What hoisting really is | 📹 Long | Creation phase memory allocation |
| 8.2 | var hoisting — set to undefined |
📹 Long | |
| 8.3 | Function declaration hoisting — fully hoisted | 📹 Long | vs function expressions |
| 8.4 | let / const — TDZ explained |
📹 Long | Hoisted but inaccessible |
| 8.5 | Class hoisting | ⚡ Short | Classes are NOT fully hoisted |
| 8.6 | Hoisting in nested functions | 📹 Long | Per-EC hoisting behavior |
Example Short: Classes aren't hoisted like functions — this one surprises everyone 😲
Example Long Video: Hoisting fully explained — var, let, const, functions, classes
thisdoesn't mean what you think it means. It depends entirely on how a function is called.
Rule 1 — Default binding (standalone call)
function greet() { console.log(this); }
greet(); → window (browser) | undefined (strict mode)
Rule 2 — Implicit binding (method call)
const obj = { name: "Rahul", greet() { console.log(this.name); } }
obj.greet(); → this = obj → "Rahul"
Rule 3 — Explicit binding (call / apply / bind)
greet.call(obj) → this = obj
greet.apply(obj) → this = obj
greet.bind(obj)() → this = obj (returns new function)
Rule 4 — new binding (constructor call)
function Person(name) { this.name = name; }
const p = new Person("Rahul"); → this = new object
const obj = {
name: "Rahul",
greet: function() {
const inner = () => {
console.log(this.name); // "Rahul" — inherits from greet's EC
};
inner();
}
};
obj.greet();| # | Topic | Format | Notes |
|---|---|---|---|
| 9.1 | What this actually refers to |
📹 Long | Call-site determination |
| 9.2 | Default binding | 📹 Long | Strict vs non-strict |
| 9.3 | Implicit binding & binding loss | 📹 Long | The callback this trap |
| 9.4 | Explicit binding — call, apply, bind | 📹 Long | Differences + use cases |
| 9.5 | new binding — constructor context |
📹 Long | What new does step by step |
| 9.6 | Arrow functions & lexical this |
📹 Long | Inheriting from enclosing EC |
| 9.7 | this in classes |
⚡ Short | Method binding in class instances |
| 9.8 | this priority — which rule wins? |
⚡ Short | new > explicit > implicit > default |
Example Short: Lost your \this` in a callback? Here's why and the fix ✅ **Example Long Video:**this — all four binding rules with real bugs and fixes **Project:**Build a custom bind() polyfill from scratch`
Closures are not a feature. They are a natural consequence of lexical scope + function EC lifetime.
function makeCounter() {
let count = 0; // lives in makeCounter's EC
return function increment() {
count++; // accesses outer EC — closure!
console.log(count);
};
}
const counter = makeCounter(); // makeCounter EC "done" — but count survives!
counter(); // 1
counter(); // 2
counter(); // 3Normal: EC runs → EC destroyed → variables gone
Closure: EC runs → inner function holds reference to outer variables
→ Garbage Collector cannot collect them
→ They stay in memory as long as inner function exists
| # | Topic | Format | Notes |
|---|---|---|---|
| 10.1 | What creates a closure | 📹 Long | Lexical scope + returned functions |
| 10.2 | Closure over the variable, not the value | 📹 Long | The classic var in loop bug |
| 10.3 | Practical closure patterns | 📹 Long | Counters, private state, once() |
| 10.4 | Closures in async code | 📹 Long | setTimeout + loop trap |
| 10.5 | Memory implications of closures | 📹 Long | When they cause leaks |
| 10.6 | Module pattern using closures | 📹 Long | Private + public API |
Example Short: The var-in-loop bug — closures explained in 90 seconds 🔄
Example Long Video: Closures — not magic, just lexical scope + persistent EC
Project: Build a private counter module using only closures — no classes
JavaScript is single-threaded. But it handles async tasks. Here's how that's possible.
┌──────────────────────────────────────────────────────────┐
│ JS ENGINE │
│ │
│ Call Stack Memory Heap │
│ ────────── ─────────── │
│ [main()] {objects, │
│ [fn()] arrays, fns │
│ stored here} │
└──────────────────────────────────────────────────────────┘
┌──────────────────┐ ┌────────────────────────────────┐
│ Web APIs │ │ TASK QUEUES │
│ (Browser/Node) │ │ │
│ setTimeout │────▶│ Macrotask Queue │
│ fetch │ │ [setTimeout cb] [setInterval] │
│ DOM events │ │ │
│ setInterval │ │ Microtask Queue (priority) │
└──────────────────┘ │ [Promise .then] [queueMicro] │
└────────────────────────────────┘
↕
EVENT LOOP
(moves tasks to Call Stack
when stack is empty)
console.log("1 - sync");
setTimeout(() => console.log("2 - macrotask"), 0);
Promise.resolve().then(() => console.log("3 - microtask"));
console.log("4 - sync");
// Output:
// 1 - sync
// 4 - sync
// 3 - microtask ← microtasks drain before macrotasks
// 2 - macrotask| # | Topic | Format | Notes |
|---|---|---|---|
| 11.1 | Why JS needs an event loop | 📹 Long | Single thread + async tasks |
| 11.2 | Web APIs — where async tasks live | 📹 Long | setTimeout, fetch, DOM events |
| 11.3 | Macrotask queue (Task Queue) | 📹 Long | setTimeout, setInterval, I/O |
| 11.4 | Microtask queue | 📹 Long | Promises, queueMicrotask, MutationObserver |
| 11.5 | Microtask vs Macrotask — priority rules | 📹 Long | Microtasks drain first, always |
| 11.6 | requestAnimationFrame timing |
⚡ Short | Where it fits in the loop |
| 11.7 | Blocking the event loop | 📹 Long | What heavy sync code does to UI |
| 11.8 | Node.js event loop differences | 📹 Long | libuv, phases, I/O callbacks |
| 11.9 | Output-based event loop questions | ⚡ Short series | Interview prep |
Example Short: Microtask vs Macrotask — which runs first? Most people get this wrong 🤯
Example Long Video: The Event Loop — complete visual walkthrough with all queues
Project: Build a task scheduler that demonstrates microtask/macrotask ordering
Memory you're not using should be freed. How does the engine decide what to free?
JavaScript manages memory automatically. But "automatic" doesn't mean "perfect." Understanding GC helps you write code that doesn't hold memory longer than needed.
Step 1 — MARK
→ Start from "roots" (global object, call stack variables)
→ Traverse all references reachable from roots
→ Mark every reachable object
Step 2 — SWEEP
→ Walk through the entire heap
→ Any object NOT marked → unreachable → free its memory
Step 3 — COMPACT (optional)
→ Defragment heap — move remaining objects together
→ Faster future allocations
// Reference counting fails on circular references:
function leak() {
let a = {};
let b = {};
a.ref = b; // a holds b
b.ref = a; // b holds a
// both have count > 0 — never freed
// mark-and-sweep handles this correctly — neither reachable from root
}| # | Topic | Format | Notes |
|---|---|---|---|
| 12.1 | How JS manages memory automatically | 📹 Long | Allocation → use → deallocation |
| 12.2 | Reachability — the core GC concept | 📹 Long | Roots, references, reachable graph |
| 12.3 | Mark-and-Sweep algorithm | 📹 Long | The modern approach |
| 12.4 | Reference counting — why it was replaced | 📹 Long | Circular reference problem |
| 12.5 | Generational GC in V8 | 📹 Long | Young gen (Scavenger) vs Old gen |
| 12.6 | GC pauses and performance | ⚡ Short | Stop-the-world vs incremental GC |
| 12.7 | WeakRef and FinalizationRegistry |
📹 Long | Modern GC-aware APIs |
| 12.8 | WeakMap / WeakSet — GC-friendly storage |
📹 Long | Keys held weakly |
Example Short: WeakMap vs Map — why one lets the GC clean up and the other doesn't 🗑️
Example Long Video: Garbage Collection in V8 — Mark-Sweep, generations, and pauses
Code that works is not the same as code that doesn't leak. This section covers the real-world causes.
// 1. Forgotten global variables
function oops() {
leaked = "I'm global now"; // no var/let/const → window.leaked
}
// 2. Detached DOM nodes held in JS
const btn = document.getElementById("btn");
document.body.removeChild(btn); // removed from DOM
// but 'btn' variable still holds reference → node can't be GC'd
// 3. Timers not cleared
const id = setInterval(() => {
doWork(bigData); // bigData stays in memory as long as timer runs
}, 1000);
// must call clearInterval(id) when done
// 4. Closures holding large data unintentionally
function setup() {
const hugeData = new Array(1000000).fill("data");
return function() {
console.log("running"); // closes over hugeData — keeps it alive
};
}| # | Topic | Format | Notes |
|---|---|---|---|
| 13.1 | What is a memory leak? | 📹 Long | Allocated but never freed |
| 13.2 | Accidental globals | 📹 Long | Missing declarations |
| 13.3 | Detached DOM nodes | 📹 Long | Common in SPAs |
| 13.4 | Unclosed timers & intervals | 📹 Long | setInterval cleanup pattern |
| 13.5 | Event listeners not removed | 📹 Long | addEventListener + removeEventListener |
| 13.6 | Closures holding large data | 📹 Long | Unintended long-lived references |
| 13.7 | Finding leaks with Chrome DevTools | 📹 Long | Heap snapshots, allocation timeline |
| 13.8 | WeakMap / WeakRef as fixes |
📹 Long | Let GC do its job |
Example Long Video: Find and fix a memory leak live — Chrome DevTools heap snapshot walkthrough
Project: Diagnose a deliberately leaky SPA — find all 4 leak types with DevTools
The advanced section. Writing code the engine can optimize — and understanding why it sometimes can't.
All functions start "unoptimized"
↓
Ignition (interpreter) runs them as bytecode
↓
Profiler watches for "hot" functions (called many times)
↓
TurboFan (JIT compiler) compiles hot functions to machine code
↓
Fast execution — until type changes cause deoptimization
Hidden classes — shape optimization
// V8 builds a "hidden class" (shape) for each object structure
// Consistent shapes = fast property access
// FAST — same shape
function Point(x, y) { this.x = x; this.y = y; }
const p1 = new Point(1, 2); // shape: {x, y}
const p2 = new Point(3, 4); // same shape → V8 can optimize
// SLOW — different shapes
const a = {};
a.x = 1; // shape A: {x}
a.y = 2; // shape B: {x, y} — new hidden class
const b = {};
b.y = 2; // shape C: {y} — different from A
b.x = 1; // shape D: {y, x} — different from B
// a and b have DIFFERENT shapes despite same final state| # | Topic | Format | Notes |
|---|---|---|---|
| 14.1 | V8 pipeline — Ignition + TurboFan | 📹 Long | Bytecode → machine code |
| 14.2 | Hidden classes (object shapes) | 📹 Long | Property order matters |
| 14.3 | Inline caching | 📹 Long | How repeated calls get faster |
| 14.4 | Deoptimization — and what triggers it | 📹 Long | Type changes, arguments object |
| 14.5 | Monomorphic vs polymorphic call sites | 📹 Long | One shape vs many shapes |
| 14.6 | Practical: how to write V8-friendly code | 📹 Long | Actionable patterns |
| 14.7 | console.time + profiler for benchmarking |
📹 Long | Measure before optimizing |
Example Short: Property order in objects affects performance — here's why ⚡
Example Long Video: How V8 JIT compiles your code — and what makes it give up
Project: Benchmark two implementations of the same function — find which V8 optimizes better
Week 1 → Sections 1 + 2 Engine overview + Memory model
Week 2 → Sections 3 + 4 Execution Context + Global EC
Week 3 → Sections 5 + 6 Function EC + Call Stack
Week 4 → Sections 7 + 8 Scope Chain + Hoisting
Week 5 → Section 9 this keyword — all four rules
Week 6 → Section 10 Closures — deep dive
Week 7 → Section 11 Event Loop — complete model
Week 8 → Sections 12 + 13 Garbage Collection + Memory Leaks
Week 9 → Section 14 V8 Internals — optimization
**Suggested video format for this series:**
Every video should open with a **wrong mental model** (what most people think), then replace it with the **correct one** (what the engine actually does). This structure converts well — viewers share the "I was wrong about this" moment.
Part of the JavaScript Complete Roadmap series. This playlist pairs with the main roadmap — specifically deepening Sections 3, 4, 8, and 9 of the core playlist.