Loading...
Loading...
Event loop, call stack, V8, and memory — the foundation every SDE-2 frontend interview tests.
Module 7 · JavaScript Internals
How JavaScript actually runs: V8 engine, call stack, memory heap, Web APIs, event loop, microtask vs callback queues, non-blocking I/O, garbage collection, and Web Workers.
JavaScript runtime environment: V8 engine, call stack, memory heap, Web APIs, event loop, microtask vs callback queues, non-blocking I/O, V8 optimization, garbage collection, and Web Workers — the foundation of every async interview question.
JavaScript Internals is the foundation of every SDE-2 frontend interview — if you understand the runtime, event loop, and memory model, async bugs, performance issues, and polyfill questions become predictable.
This module walks through the full runtime environment with step-by-step examples: call stack traces, microtask vs macrotask ordering, debounce/throttle internals, V8 optimization, GC, and Web Workers.
Mastering this module means you can predict async output in whiteboard questions, explain why the UI freezes during heavy synchronous work, and describe how the browser keeps I/O non-blocking despite JavaScript being single-threaded.
Covers the runtime vs engine split, call stack overflow, microtask priority over macrotasks, Promises and async/await ordering, closure memory, V8 hidden classes, mark-and-sweep GC, and moving heavy work off-thread with Web Workers.
Event loop questions appear in nearly every SDE-2 frontend loop. Understanding runtime internals explains Promise ordering, why setTimeout(fn, 0) isn't instant, how debounce works, and how to debug memory leaks and UI freezes. This module connects browser APIs to the single-threaded JavaScript model.
The full environment that executes your code — not just the engine.
The JavaScript Runtime is the environment that runs JavaScript. Think of it as a house: the engine is the furnace, but the house also needs plumbing (Web APIs), storage (memory heap), a to-do list (queues), and a manager (event loop).
JavaScript Engine
V8 — parse, compile, execute
Call Stack
LIFO — which function is running
Memory Heap
Objects, arrays, closures
Web APIs
setTimeout, fetch, DOM
Microtask Queue
Promises, queueMicrotask
Callback Queue
setTimeout, events, I/O
Event Loop
Routes tasks → call stack
JavaScript Runtime ├─ JavaScript Engine (V8, SpiderMonkey) ├─ Call Stack ├─ Memory Heap ├─ Web APIs (Browser) ├─ Callback Queue ├─ Microtask Queue └─ Event Loop
Interview one-liner
Parses, compiles, executes — V8, SpiderMonkey, JavaScriptCore.
| Engine | Browser / Runtime |
|---|---|
| V8 | Chrome, Node.js, Deno, Edge |
| SpiderMonkey | Firefox |
| JavaScriptCore (JSC) | Safari |
| Chakra | Edge (legacy) |
V8 Engine Pipeline
V8 Engine Pipeline ├─ Parser (Source Code → AST) ├─ Ignition (AST → Bytecode, fast startup) ├─ TurboFan (Bytecode → Optimized native code) └─ Garbage Collector (generational memory management)
Ignition vs TurboFan
| Aspect | JavaScript Engine | JavaScript Runtime |
|---|---|---|
| What | Compiler/parser only | Full execution environment |
| Examples | V8, SpiderMonkey | Browser, Node.js, Deno |
| Contains | Parser, compiler, optimizer, GC | Engine + stack + heap + APIs + queues |
| Analogy | Car engine | Entire car |
LIFO stack tracking which function is currently executing.
The call stack is a stack data structure (Last-In, First-Out). Only one function runs at a time — JavaScript is single-threaded. When a function is called, it's pushed; when it returns, it's popped.
function a() { console.log("a start"); b(); console.log("a end"); } function b() { console.log("b start"); c(); console.log("b end"); } function c() { console.log("c — done"); } a();
Output
a start b start c — done b end a end
Call stack flow
Stack overflow
Where primitives and objects live in memory.
| Aspect | Memory Stack | Memory Heap |
|---|---|---|
| Stores | Primitives, references | Objects, functions, arrays, closures |
| Size | Static (fixed per frame) | Dynamic (grows at runtime) |
| Access | Fast | Slower lookup |
| Allocation | Automatic on function call | Manual via new / literals |
// Stack: primitive values + references let count = 42; // number on stack let name = "Ada"; // string reference on stack // Heap: the actual object const user = { id: 1, name: "Ada" }; // object on heap const nums = [1, 2, 3]; // array on heap
Closures live on the heap
Browser-provided async capabilities — not part of the JS language.
Web APIs are provided by the browser (or Node.js libuv in Node) — not the JavaScript engine. They look like JavaScript functions but run on separate threads. When done, they push callbacks into queues.
Key point
console.log("1 — sync, call stack"); setTimeout(() => { console.log("3 — callback queue (via Web API timer)"); }, 0); console.log("2 — sync, call stack");
Output
1 — sync, call stack 2 — sync, call stack 3 — callback queue (via Web API timer)
setTimeout(fn, 0) is not instant
The coordinator between queues and the call stack.
The event loop continuously checks: is the call stack empty? If yes, drain all microtasks, then run one macrotask. Repeat. It never interrupts running synchronous code.
Interactive · Event loop demo
console.log("1");
setTimeout(() => console.log("4"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("2");Idle
Call stack
Microtask queue
Macrotask queue
Console output
—
while (running) { // 1. If call stack not empty → keep executing // 2. If call stack empty: // a. Execute ALL microtasks (FIFO) // b. Execute ONE callback (FIFO) // c. Repeat }
Golden rule
Two queues, different priorities — this is the #1 interview topic.
Callback Queue (Task Queue)
Holds callback functions ready to execute. FIFO. Handles setTimeout, setInterval, I/O, DOM events. Lower priority than microtasks.
setTimeout(() => { console.log("timeout callback"); }, 0);
Microtask Queue
Higher priority than Callback Queue. Handles Promises, queueMicrotask(), MutationObserver.
console.log("start"); setTimeout(() => console.log("timeout"), 0); Promise.resolve().then(() => console.log("promise")); console.log("end");
Output
start end promise ← microtask (runs first) timeout ← callback (runs after)
| Aspect | Microtask Queue | Callback Queue (Macrotask) |
|---|---|---|
| Priority | Higher | Lower |
| Executes per tick | All pending microtasks | Exactly one task |
| FIFO | Yes | Yes |
| Sources | Promise.then, queueMicrotask, MutationObserver | setTimeout, setInterval, I/O, DOM events |
| Runs when | After sync code, before macrotasks | After all microtasks drained |
Memory trick
Step through these until the pattern is automatic.
Example 1 — Classic interview question
console.log("A"); setTimeout(() => console.log("B"), 0); Promise.resolve().then(() => console.log("C")); console.log("D");
Output
A D C ← microtask (Promise) B ← macrotask (setTimeout)
Step-by-step execution
Sync first (A, D). Stack empty → all microtasks (C). Then one macrotask (B).
Example 2 — queueMicrotask + nested microtasks
console.log("1"); setTimeout(() => console.log("2"), 0); Promise.resolve().then(() => console.log("3")); queueMicrotask(() => console.log("4")); console.log("5");
Output
1 5 4 ← queueMicrotask (FIFO among microtasks) 3 ← Promise.then 2 ← setTimeout
Example 3 — Microtask inside macrotask
console.log("start"); setTimeout(() => { console.log("timeout"); Promise.resolve().then(() => console.log("promise inside timeout")); }, 0); Promise.resolve().then(() => console.log("promise")); console.log("end");
Output
start end promise timeout promise inside timeout ← microtask drains after its macrotask
From source code to event loop — the full pipeline.
Interview tip
Async work happens off the main thread — the event loop picks up results.
When you call fetch(url), the network request runs in a Web API thread. JavaScript continues immediately. When the response arrives, the callback enters the queue and the event loop runs it when the stack is free.
console.log("Request sent"); fetch("/api/users") .then((res) => res.json()) .then((users) => console.log("Got users:", users.length)); console.log("UI still responsive");
Output
Request sent UI still responsive Got users: 42 ← later, when network completes
How single-threaded JS handles async without blocking.
JavaScript handles async via four cooperating pieces: the event loop manages scheduling, the callback queue holds macrotasks, the microtask queue holds promise callbacks, and Web APIs run async work off the main thread.
| Component | Purpose | Priority | FIFO / LIFO |
|---|---|---|---|
| Call Stack | Track running functions | Highest | LIFO |
| Microtask Queue | Promises, queueMicrotask | High | FIFO |
| Callback Queue | setTimeout, events, I/O | Low | FIFO |
| Event Loop | Manage queues → stack | N/A | N/A |
| Web APIs | Async operations | N/A | N/A |
| Memory Heap | Objects, functions | N/A | N/A |
| Memory Stack | Primitives | N/A | N/A |
Pros, cons, and the Web Worker escape hatch.
| Detail | |
|---|---|
| Why | Simpler design, no race conditions on DOM, easier to implement |
| Pros | No thread locks, no synchronization bugs, easier debugging |
| Cons | CPU-heavy work blocks UI, no parallel computation on main thread |
| Solution | Async I/O via event loop + Web Workers for parallel CPU work |
Blocking example
How Chrome and Node.js compile JavaScript for speed.
Automatic memory management in V8.
Common leak sources
True parallel JavaScript — separate thread, no DOM access.
// main.js const worker = new Worker("worker.js"); worker.postMessage({ numbers: [1, 2, 3, 4, 5] }); worker.onmessage = (event) => { console.log("Result from worker:", event.data); }; // worker.js self.onmessage = (event) => { const sum = event.data.numbers.reduce((a, b) => a + b, 0); self.postMessage(sum); };
Ten points to nail the JavaScript runtime question.
Common interview questions about JavaScript internals.