JavaScript Internals 2026: V8 Engine, Event Loop, Closures & More
The ultimate guide to JavaScript internals. Master V8 engine, hoisting, closures, event loop, and memory management with visual diagrams.
JavaScript Internals
How JS Works Under the Hood - From Engine to Event Loop
Every line of JavaScript you write goes through a breathtaking journey - from raw text to optimized machine code, through execution contexts, call stacks, and event loops. This is the most comprehensive guide on the internet to understanding how JavaScript actually works under the hood. Whether you're preparing for a senior-level interview or simply want to write better code, mastering these internals is what separates good developers from great ones.
Who is this for?
This 20-chapter deep-dive covers everything from V8 engine internals to garbage collection. It's designed for developers who want to go beyond syntax and truly understand the mechanics behind their code. Diagrams, code examples, and real-world analogies are used throughout.
Table of Contents
The Big Picture
Before diving deep, let's trace the complete journey of your JavaScript code - from the moment you write it to the moment it produces results on screen.
JavaScript Code Lifecycle
You write JS code
โ
JS Engine receives it (V8 in Chrome/Node, SpiderMonkey in Firefox)
โ
Parser โ tokenizes โ builds AST (Abstract Syntax Tree)
โ
Interpreter (Ignition) โ converts to Bytecode (runs immediately)
โ
Profiler watches โ hot code โ JIT Compiler (TurboFan) โ optimized Machine Code
โ
Global Execution Context created
โ
Memory Phase โ Code Phase
โ
Call Stack manages execution
โ
Async work โ Web APIs / libuv โ Queues โ Event Loop โ Call Stack4 Things That Define JavaScript
Single-Threaded
One call stack, one thing at a time. No parallel execution in the main thread.
Non-Blocking
Async operations are offloaded so the main thread never freezes.
Dynamically Typed
Types are checked at runtime, not compile time. Variables can hold any type.
JIT Compiled
Not purely interpreted or compiled - it's a hybrid approach for maximum performance.
Why This Matters
JavaScript Engine
The engine is the program that reads and executes your JavaScript. Different browsers and runtimes use different engines, but they all follow the same fundamental principles.
| Environment | Engine | Written In |
|---|---|---|
| Chrome / Node.js | V8 | C++ |
| Firefox | SpiderMonkey | C / C++ |
| Safari | JavaScriptCore (Nitro) | C++ |
| Edge (Legacy) | Chakra | C++ |
Inside the V8 Engine
V8 has two main components that work together to execute your code:
V8 Engine Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ V8 ENGINE โ โ โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ โ โ โ Memory โ โ Call Stack โ โ โ โ Heap โ โ โ โ โ โ โ โ (where code โ โ โ โ (where objs โ โ executes) โ โ โ โ are stored) โ โ โ โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Memory Heap
An unstructured memory pool where objects, arrays, and functions are stored. Memory allocation happens here dynamically as your program runs.
Call Stack
A LIFO data structure that tracks which function is currently executing. Every function call pushes a new frame; every return pops one off.
How JS Code is Compiled
JavaScript uses JIT (Just-In-Time) Compilation - a hybrid approach that combines the fast startup of an interpreter with the raw speed of a compiler.
JIT Compilation Pipeline
Source Code
โ
โโโโโโโโโโโโโโโ
โ PARSER โ โ checks syntax, tokenizes code
โโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโ
โ AST โ โ Abstract Syntax Tree (tree structure of your code)
โโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโ
โ INTERPRETER โ โ converts to Bytecode, starts executing immediately
โ (Ignition) โ (fast startup, slower execution)
โโโโโโโโโโโโโโโ
โ (profiler detects "hot" functions - called repeatedly)
โโโโโโโโโโโโโโโ
โJIT COMPILER โ โ recompiles hot code into optimized Machine Code
โ (TurboFan) โ (slower to compile, much faster to run)
โโโโโโโโโโโโโโโ
โ
Machine Code runs directly on CPUPerformance Insight
What is the AST?
The Abstract Syntax Tree is a tree representation of your code's structure. Every expression, statement, and declaration becomes a node in this tree.
// Your code:
const x = 5 + 3
// AST representation (simplified):
{
type: "VariableDeclaration",
kind: "const",
declarations: [{
type: "VariableDeclarator",
id: { type: "Identifier", name: "x" },
init: {
type: "BinaryExpression",
operator: "+",
left: { type: "Literal", value: 5 },
right: { type: "Literal", value: 3 }
}
}]
}
// Visualize any code's AST at โ astexplorer.netExecution Context
An Execution Context (EC) is the environment in which JavaScript code is evaluated and executed. Think of it as a container that holds everything needed to run a piece of code.
Three Types of Execution Context
Global Execution Context
Created once when the script starts. this = window (browser) or module (Node.js). Only one exists per program.
Function Execution Context
Created every time a function is called. Each call gets a brand-new EC, even recursive calls to the same function.
Eval Execution Context
Created inside eval(). Avoid using eval - it's a security risk and blocks engine optimizations.
Inside an Execution Context
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ EXECUTION CONTEXT โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ Variable Environment โ โ โ โ (var declarations, functions) โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ Lexical Environment โ โ โ โ (let, const, outer reference) โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ 'this' binding โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Memory Phase & Code Phase
Every Execution Context is created in two distinct phases. Understanding these phases is the key to understanding hoisting.
Phase 1 - Memory Creation (Hoisting Phase)
The JS engine scans the entire code BEFORE executing a single line:
Memory Allocation Rules
For var declarations โ stored with value: undefined For let/const โ stored but in Temporal Dead Zone (TDZ) For function declarations โ stored with FULL function definition For function expressions โ treated like var (undefined)
// What you write:
console.log(a) // undefined (not error - var hoisted)
console.log(b) // ReferenceError - TDZ
console.log(greet) // [Function: greet] - fully hoisted
var a = 10
let b = 20
function greet() { return "hello" }Memory Phase - Before Any Code Runs
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Variable Environment โ
โ โ
โ a โ undefined โ
โ b โ <TDZ - uninitialized> โ
โ greet โ function greet() {...} โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโPhase 2 - Code Execution Phase
Code runs line by line, top to bottom. Values get assigned to previously allocated memory slots.
Code Execution - Line by Line
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Line 4: a = 10 โ a updated from undefined to 10 โ โ Line 5: b = 20 โ b initialized, TDZ ends โ โ Line 6: greet already stored - no update needed โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Hoisting
Hoisting is the behavior where declarations are processed during the Memory Phase before any code executes. It's not that code physically moves - rather, the engine already knows about declarations before running anything.
var Hoisting
console.log(x) // undefined (hoisted, not initialized)
var x = 5
console.log(x) // 5
// JS sees it as:
var x // declaration hoisted to top
console.log(x) // undefined
x = 5 // assignment stays here
console.log(x) // 5let & const - Temporal Dead Zone (TDZ)
console.log(y) // โ ReferenceError: Cannot access 'y' before initialization
let y = 10
// let and const ARE hoisted (JS knows they exist)
// but they're in TDZ from start of scope until declaration line
// accessing them in TDZ = ReferenceErrorTDZ Visualized
โโโ start of scope โโโโโโโโโโโโโโโโโโโโโโโโโ โ โ TDZ for y (exists but not accessible) โ โ โ TDZ for y โ โ let y = 10 โ TDZ ENDS here โ โ console.log(y) โ safe to access now โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Function Hoisting
// Function DECLARATION - fully hoisted
greet() // โ
"Hello" - works before declaration
function greet() { console.log("Hello") }
// Function EXPRESSION - NOT fully hoisted (treated like var)
sayHi() // โ TypeError: sayHi is not a function
var sayHi = function() { console.log("Hi") }
// Arrow function - same as function expression
sayBye() // โ TypeError
var sayBye = () => console.log("Bye")Hoisting Summary Table
| Declaration | Hoisted? | Initial Value | Accessible Before Init? |
|---|---|---|---|
var | โ Yes | undefined | โ Yes (gets undefined) |
let | โ Yes | TDZ | โ No (ReferenceError) |
const | โ Yes | TDZ | โ No (ReferenceError) |
function declaration | โ Yes | Full definition | โ Yes |
function expression | โ (as var) | undefined | โ No (TypeError) |
class | โ Yes | TDZ | โ No (ReferenceError) |
The Call Stack
The Call Stack is a LIFO (Last In, First Out) data structure that tracks execution contexts. JS can only execute what's on top of the stack.
function multiply(a, b) {
return a * b
}
function square(n) {
return multiply(n, n)
}
function printSquare(n) {
const result = square(n)
console.log(result)
}
printSquare(4) // Output: 16Call Stack Step-by-Step
Step 1: Script starts Step 2: printSquare(4)
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ Global EC โ โ only โ printSquare EC โ โ pushed
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโค
โ Global EC โ
โโโโโโโโโโโโโโโโโโโโโโโโ
Step 3: square(4) Step 4: multiply(4,4)
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ square EC โ โ pushed โ multiply EC โ โ TOP
โโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโโค
โ printSquare EC โ โ square EC โ
โโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโโค
โ Global EC โ โ printSquare EC โ
โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโค
โ Global EC โ
โโโโโโโโโโโโโโโโโโโโโโโโ
Step 5: multiply returns 16 โ POPPED
Step 6: square returns 16 โ POPPED
Step 7: console.log(16) โ POPPED
Step 8: printSquare done โ POPPED
Step 9: Global EC โ POPPED (program ends)Stack Overflow
function infinite() {
return infinite() // calls itself forever
}
infinite()
// โ RangeError: Maximum call stack size exceeded
// Stack grows until memory runs out - this is a "stack overflow"Interview Tip
Scope & Scope Chain
Scope determines where a variable is accessible. JavaScript has three types of scope, and they nest to form a scope chain.
Three Types of Scope
// 1. GLOBAL SCOPE
var globalVar = "I'm everywhere"
function example() {
// 2. FUNCTION SCOPE
var funcVar = "only inside this function"
if (true) {
// 3. BLOCK SCOPE (let/const only)
let blockVar = "only inside this block"
var notBlockScoped = "I leak out to function scope"
}
console.log(notBlockScoped) // โ
accessible (var ignores blocks)
console.log(blockVar) // โ ReferenceError (let is block scoped)
}The Scope Chain
When JS looks for a variable, it searches up the chain: Current scope โ Outer scope โ ... โ Global scope โ ReferenceError
const a = 'global'
function outer() {
const b = 'outer'
function middle() {
const c = 'middle'
function inner() {
const d = 'inner'
console.log(d) // โ
found in own scope
console.log(c) // โ
found in middle scope (1 level up)
console.log(b) // โ
found in outer scope (2 levels up)
console.log(a) // โ
found in global scope (3 levels up)
console.log(e) // โ ReferenceError - not found anywhere
}
inner()
}
middle()
}
outer()Scope Chain Lookup Path
inner() scope โ middle() scope โ outer() scope โ Global scope โ โ ReferenceError
d c b aKey Insight
Closures
A closure is a function that remembers the variables from its outer scope even after the outer function has finished executing. This is one of the most powerful and commonly misunderstood concepts in JavaScript.
function makeCounter() {
let count = 0 // this variable "closes over" into inner function
return function() {
count++
return count
}
}
const counter = makeCounter()
// makeCounter() has finished - its EC is gone from call stack
// BUT count still lives because the inner function holds a reference
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3How Closures Work Internally
makeCounter() finishes and pops off call stack BUT the returned function holds a reference to: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Closure (makeCounter scope) โ โ count: 0 โ 1 โ 2 โ 3 โ โ (stays in memory heap) โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Garbage collector won't clean this because the inner function still references it
Real-World Uses of Closures
// 1. DATA PRIVACY (encapsulation)
function createBankAccount(initialBalance) {
let balance = initialBalance // private - can't access directly
return {
deposit: (amount) => { balance += amount },
withdraw: (amount) => { balance -= amount },
getBalance: () => balance
}
}
const account = createBankAccount(1000)
account.deposit(500)
console.log(account.getBalance()) // 1500
console.log(account.balance) // undefined - no direct access!
// 2. FUNCTION FACTORIES
function multiply(x) {
return (y) => x * y // closes over x
}
const double = multiply(2)
const triple = multiply(3)
double(5) // 10
triple(5) // 15
// 3. MEMOIZATION
function memoize(fn) {
const cache = {} // closes over cache
return function(n) {
if (cache[n]) return cache[n]
cache[n] = fn(n)
return cache[n]
}
}Classic Closure Gotcha
// โ BUG - var doesn't create new scope per iteration
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000)
}
// Output: 3, 3, 3 (NOT 0, 1, 2)
// All closures share the SAME i (var is function scoped)
// โ
FIX 1 - use let (block scoped, new binding per iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000)
}
// Output: 0, 1, 2
// โ
FIX 2 - use IIFE to create new scope
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 1000)
})(i)
}The this Keyword
this refers to the object that is currently executing the function. Unlike most languages, it's determined at call time, not definition time (except arrow functions).
5 Rules of this
// RULE 1: Global context
console.log(this) // window (browser) / {} (Node module)
// RULE 2: Object method - this = the object
const user = {
name: "Dev Kant",
greet() { console.log(this.name) } // "Dev Kant"
}
user.greet()
// RULE 3: Regular function - this = undefined (strict) or window
function standalone() {
console.log(this) // undefined in strict mode
}
standalone()
// RULE 4: Arrow function - this = inherited from surrounding scope
const obj = {
name: "Dev Kant",
greet: () => {
console.log(this.name) // undefined! arrow has no own 'this'
},
greetCorrect() {
const arrow = () => console.log(this.name) // โ
inherits
arrow()
}
}
// RULE 5: Constructor / new keyword - this = newly created object
function Person(name) {
this.name = name // this = new empty object
}
const dev = new Person("Dev Kant")
console.log(dev.name) // "Dev Kant"Explicitly Setting this
function introduce(greeting) {
console.log(`${greeting}, I'm ${this.name}`)
}
const person = { name: "Dev Kant" }
// call - invoke immediately, pass args individually
introduce.call(person, "Hello")
// apply - invoke immediately, pass args as array
introduce.apply(person, ["Hi"])
// bind - returns NEW function with this permanently bound
const boundIntroduce = introduce.bind(person)
boundIntroduce("Hey")this Summary Table
| Context | this value |
|---|---|
| Global (browser) | window |
| Global (Node strict) | {} |
| Object method | The object |
| Regular function | undefined (strict) / window |
| Arrow function | Lexical (from surrounding scope) |
new keyword | Newly created object |
call / apply / bind | Whatever you pass |
Prototype & Prototype Chain
Every object in JS has a hidden [[Prototype]] property pointing to another object. This forms the prototype chain - JavaScript's inheritance system.
const arr = [1, 2, 3]
// arr has access to .map(), .filter(), .push() etc.
// but YOU didn't define them - where do they come from?
// Answer: arr โ Array.prototype โ Object.prototype โ null
function Dog(name) {
this.name = name
}
Dog.prototype.bark = function() {
console.log(`${this.name} says Woof!`)
}
const dog1 = new Dog("Bruno")
dog1.bark() // "Bruno says Woof!"
// When JS looks up dog1.bark:
// 1. Check dog1 own properties โ not found
// 2. Check Dog.prototype โ found! โ
execute itPrototype Chain
dog1 object
name: "Bruno"
[[Prototype]] โ Dog.prototype
bark: function
[[Prototype]] โ Object.prototype
toString: function
hasOwnProperty: function
[[Prototype]] โ null (end of chain)ES6 Classes = Prototype Syntax Sugar
// Class syntax (modern, clean)
class Animal {
constructor(name) { this.name = name }
speak() { console.log(`${this.name} makes a sound`) }
}
class Dog extends Animal {
speak() { console.log(`${this.name} barks`) }
}
// Under the hood, JS is STILL using prototypes
// 'extends' sets up the prototype chain
// 'super()' calls the parent constructor
// Classes are just a cleaner API for the same mechanismKey Takeaway
extends keyword simply creates the prototype chain for you.Asynchronous JavaScript
JS is single-threaded - it can only do one thing at a time. But the real world needs async operations: network requests, timers, file reads. How does JS handle this without freezing?
The Problem
// If JS were purely synchronous:
const data = fetch('https://api.example.com/data') // takes 2 seconds
console.log(data) // everything FREEZES for 2 seconds
// User can't click, scroll, type - completely blockedThe Solution - Offloading to the Environment
JS doesn't handle async work itself. It hands it off to the environment:
Browser โ Web APIs
fetch, setTimeout, DOM events, localStorage
Node.js โ libuv
File system, network, timers, crypto
Async Offloading Flow
JS (single thread) Environment (multi-threaded)
โ โ
โโโ setTimeout(fn, 2000) โโโโโโโโโโโโโโโถโ Timer starts here
โ โ JS moves on immediately
โโโ fetch(url) โโโโโโโโโโโโโโโโโโโโโโโโโถโ HTTP request here
โ โ
โ (continues executing other code) โ (handling async in bg)
โ โ
โ โโโโโโโโโโโโโโ Timer done, response ready
โ (callback/promise pushed to queue)
โ
Event Loop picks up from queue when call stack is emptyWeb APIs & libuv
In the Browser - Web APIs
| API | Purpose |
|---|---|
| setTimeout / setInterval | Timer API |
| fetch / XMLHttpRequest | Network API |
| addEventListener | DOM Events API |
| localStorage | Storage API |
| Geolocation | Device API |
In Node.js - libuv
libuv is a C++ library that gives Node.js async I/O capabilities.
| Operation | Handled By |
|---|---|
| File System (fs) | libuv thread pool |
| Network (http) | OS kernel (epoll/kqueue) |
| DNS lookups | libuv thread pool |
| Crypto (heavy) | libuv thread pool |
| Timers | libuv timer mechanism |
Node.js Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Your JS Code โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ Node.js APIs โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ V8 Engine โ libuv โ โ (executes JS) โ (async I/O) โ โ โ - Thread Pool (4) โ โ โ - Event Loop โ โ โ - OS Async โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Task Queue & Microtask Queue
When async operations complete, their callbacks don't go directly to the call stack. They wait in queues, each with different priorities.
Two Queues, Different Priorities
๐ฅ Microtask Queue (HIGH PRIORITY)
- Promise .then() / .catch() / .finally()
- async/await continuations
- queueMicrotask()
- MutationObserver callbacks
โ Processed COMPLETELY before any macrotask
๐ Macrotask Queue (LOWER PRIORITY)
- setTimeout / setInterval callbacks
- setImmediate (Node.js)
- I/O callbacks
- UI rendering (browser)
Proof - Queue Priority
console.log('1 - synchronous')
setTimeout(() => console.log('2 - macrotask'), 0)
Promise.resolve().then(() => console.log('3 - microtask'))
console.log('4 - synchronous')
// OUTPUT:
// 1 - synchronous
// 4 - synchronous
// 3 - microtask โ runs BEFORE macrotask even with 0ms timeout!
// 2 - macrotaskThis Is an Interview Favorite!
The Event Loop
The Event Loop is the heart of JavaScript's asynchronous model. It bridges the call stack to the task queues, ensuring non-blocking execution.
The Event Loop Algorithm
Event Loop Tick - Exact Order
1. Execute ALL synchronous code (drain the call stack) 2. Process ALL microtasks (drain microtask queue completely) โ if microtasks add more microtasks, process those too โ keep going until microtask queue is EMPTY 3. Pick ONE macrotask from macrotask queue 4. Execute it (may add to call stack) 5. Process ALL microtasks again (step 2) 6. Render (browser only - if needed) 7. Pick next macrotask (step 3) 8. Repeat forever
Full Event Loop Diagram
Complete Event Loop Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOUR CODE โ
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CALL STACK โ
โ โโโโโโโโโโโโโโโโ โ
โ โ current fn โ โ executes one thing at a time โ
โ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ empty? โ async call?
โ โผ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ WEB APIs / libuv โ
โ โ setTimeout โ timer โ
โ โ fetch โ HTTP request โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
โ โ work done
โ โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โ โโโโโโผโโโโโโโโโโโ โโโโโโโโโโโโโโโโผโโโ
โ โ MICROTASK โ โ MACROTASK โ
โ โ QUEUE โ โ QUEUE โ
โ โ (promises) โ โ (setTimeout etc) โ
โ โโโโโโฌโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ โ priority โ โ lower priority
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โ
EVENT LOOP
(bridges queues โ stack)
checks: is call stack empty?
โ yes: push next task
โ no: waitMicrotask Starvation (Edge Case)
// โ ๏ธ This will NEVER execute the setTimeout!
function keepAddingMicrotasks() {
Promise.resolve().then(keepAddingMicrotasks)
// keeps adding to microtask queue infinitely
// event loop never gets to macrotask queue
}
keepAddingMicrotasks()
setTimeout(() => console.log('never runs'), 0)
// The microtask queue never empties, so the macrotask
// (setTimeout callback) is starved and never executesCritical Warning
Promises Internals
A Promise is an object representing the eventual completion or failure of an asynchronous operation.
Three States of a Promise
Promise State Machine
PENDING โโโโ resolve(value) โโโโโถ FULFILLED โ
โ
โโโโโโโโ reject(reason) โโโโโถ REJECTED โ
Once settled (fulfilled or rejected), state NEVER changes.
A promise can only transition ONCE.How Promises Work Internally
// When you create a Promise:
const p = new Promise((resolve, reject) => {
// executor runs SYNCHRONOUSLY immediately
setTimeout(() => resolve(42), 1000)
})
// p is in PENDING state right now
p.then(value => console.log(value))
// .then() registers a callback - doesn't run yet
// when resolve(42) fires after 1 second:
// โ callback goes to MICROTASK QUEUE
// โ event loop picks it up โ runs it โ prints 42Promise Chaining
fetch('/api/user')
.then(response => response.json()) // returns new Promise
.then(data => data.name) // returns new Promise
.then(name => console.log(name)) // chain continues
.catch(err => console.error(err)) // catches any error above
.finally(() => console.log('done')) // always runs
// Each .then() returns a NEW Promise - this enables chainingPromise Static Methods
| Method | Resolves When | Rejects When |
|---|---|---|
| Promise.all() | ALL resolve | ANY rejects |
| Promise.allSettled() | ALL settle (always) | Never rejects |
| Promise.race() | First to settle | First to settle |
| Promise.any() | First SUCCESS | ALL reject |
async/await Internals
async/await is syntactic sugar over Promises. It makes async code look synchronous while behaving the same way under the hood.
// Promise version
function getUser() {
return fetch('/api/user')
.then(res => res.json())
.then(data => data.name)
}
// async/await version - same thing, cleaner syntax
async function getUser() {
const res = await fetch('/api/user') // pauses HERE
const data = await res.json() // pauses HERE
return data.name
}What Actually Happens with await
async function example() {
console.log('A')
const result = await Promise.resolve(42)
// Everything AFTER await goes into microtask queue
console.log('B', result) // this runs later
}
console.log('1')
example()
console.log('2')
// Output:
// 1
// A โ async function starts synchronously
// 2 โ continues synchronous code after example() call
// B 42 โ microtask runs after call stack clearsasync/await Execution Flow
async function hits await
โ
pauses function execution
registers continuation as microtask
โ
returns control to caller
โ
caller continues synchronously
โ
call stack empties
โ
event loop picks up continuation from microtask queue
โ
async function resumes after await lineError Handling with try/catch
async function getData() {
try {
const res = await fetch('/api/data')
if (!res.ok) throw new Error('HTTP error!')
const data = await res.json()
return data
} catch (err) {
console.error('Failed:', err.message)
} finally {
console.log('cleanup always runs')
}
}Memory Management & Garbage Collection
JS automatically manages memory - you don't free it manually like C/C++. Understanding how it works helps you avoid memory leaks.
Memory Lifecycle
Three Stages of Memory
1. ALLOCATE โ JS allocates memory when you declare variables, objects 2. USE โ read/write the value 3. RELEASE โ garbage collector frees memory when no longer needed
Garbage Collection - Mark & Sweep
V8 Mark & Sweep Algorithm
1. Start from "roots" (global variables, call stack variables) 2. Mark everything reachable from roots 3. Sweep (delete) everything NOT marked 4. Compact memory (optional - reduces fragmentation) "Reachable" = can be accessed from root via references "Unreachable" = garbage, will be collected
Common Memory Leaks
// โ LEAK 1: Accidental globals
function leak() {
forgotVar = "I'm global now" // no let/const/var โ goes to window
}
// โ LEAK 2: Forgotten timers
const data = hugeDataSet
setInterval(() => {
processData(data) // data is never freed, timer keeps reference
}, 1000)
// Always clearInterval() when done!
// โ LEAK 3: Detached DOM nodes
let element = document.getElementById('btn')
document.body.removeChild(element) // removed from DOM
// BUT element variable still holds reference - NOT garbage collected
element = null // โ
fix: explicitly null the reference
// โ LEAK 4: Closures holding large data
function process() {
const bigData = new Array(1000000).fill('data')
return function() {
// closes over bigData even if not using it
return 'done'
}
}Pro Tip
Complete Flow Diagram
Here's the master diagram that ties every concept together - from source code to execution to async handling. This is the single most important visualization in this entire guide.
JavaScript Complete Flow - Master Diagram
SOURCE CODE
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ JS ENGINE (V8) โ
โ Parser โ โ tokenize โ AST
โ Ignition โ โ bytecode (fast start)
โ TurboFan โ โ machine code (hot paths)
โโโโโโโโโโโโฌโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GLOBAL EXECUTION CONTEXT โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ MEMORY PHASE โ โ
โ โ var โ undefined โ โ
โ โ let/const โ TDZ โ โ
โ โ function โ full definition โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ CODE PHASE โ โ
โ โ line by line execution โ โ
โ โ values assigned โ โ
โ โ functions called โ new EC created โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CALL STACK โ โ WEB APIs / libuv โ
โ โโโโโโถโ setTimeout โโโถ timer โ
โ [current EC] โ โ fetch โโโโโโโถ HTTP โ
โ [outer EC] โ โ fs.read โโโโโถ disk I/O โ
โ [Global EC] โโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โฒ โ done
โ โโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โ โโโโโโโโผโโโโโโโโ โโโโโโโโโโโโโโโผโโ
โ โ MICROTASK โ โ MACROTASK โ
โ โ QUEUE โ โ QUEUE โ
โ โ Promise.then โ โ setTimeout cb โ
โ โ await cont. โ โ setInterval โ
โ โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโโ
โ โ priority 1 โ priority 2
โ โโโโโโโโโโฌโโโโโโโโโโ
โ โโโโโโโโโโผโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโ EVENT LOOP โ
โ 1. stack empty? โ
โ 2. drain micros โ
โ 3. one macro โ
โ 4. repeat โ
โโโโโโโโโโโโโโโโโโโโKeep Learning - Related Articles
These posts complement what you just learned. Reading them together will solidify your understanding.
100+ JavaScript Interview Questions & Answers 2026
Put your internals knowledge to the test. Covers closures, event loop, promises, hoisting, and more with answers.
JavaScript ยท InterviewReact Server Components: Complete Guide 2026
Understanding JS internals makes RSC click. Learn how React leverages the event loop, async rendering, and suspense.
React ยท ArchitectureReact State Management 2026
Closures and execution contexts are at the heart of hooks and state management. Connect the dots here.
React ยท StateQuick Reference Cheatsheet
Execution Context
Global EC โ created once, window/global as 'this' Function EC โ created on every function call Both have โ Memory Phase + Code Phase
Hoisting
var โ hoisted, initialized to undefined let/const โ hoisted, in TDZ (ReferenceError) function โ fully hoisted with body func expr โ hoisted as var (undefined)
Scope
var โ function scoped (ignores blocks) let/const โ block scoped Scope chain โ inner โ outer โ global โ ReferenceError
Closures
Function + its outer scope references = closure Outer function can finish, inner still has access Used for: data privacy, factories, memoization
this Keyword
Global โ window / undefined (strict) Method โ the object Arrow fn โ lexical (from surrounding scope) new โ new object call/applyโ explicitly set
Async Priority Order
1. Synchronous code (call stack) 2. Microtask queue (ALL) - Promises, await 3. ONE Macrotask - setTimeout, I/O 4. Microtask queue again (ALL) 5. Next Macrotask - repeat
Memory
Heap โ objects, arrays, functions stored here Stack โ primitives + references stored here GC โ mark & sweep, removes unreachable objects Leaks โ globals, forgotten timers, detached DOM, closures
Recommended Next Steps
Notes by Dev Kant Kumar | JavaScript Mastery Journey 2026
Build a Consistent Coding Habit
Stop guessing and start building. This e-book provides practical strategies, exercises, and routines to help you code regularly and improve steadily.
Get E-BookMaster Unfamiliar Codebases
Struggling to make sense of someone else's code? Learn practical strategies to navigate, analyze, and master unfamiliar codebases with confidence.
Get E-Book
๐ฌ Discussion