Munsif.
AboutExperienceProjectsAchievementsBlogsContact
HomeAboutExperienceProjectsAchievementsBlogsContact
Munsif.

Frontend Developer crafting scalable web applications with modern technologies and clean code practices.

Quick Links

  • About
  • Experience
  • Projects
  • Achievements

Connect

© 2026 Shaik Munsif. All rights reserved.

Built with Next.js & Tailwind

0%
Welcome back!Continue where you left off
Back to Blogs
JavaScript

JavaScript Runtime Deep Dive: Event Loop, Call Stack & Async Execution

Master the JavaScript Runtime from the inside out. Understand the Call Stack, Memory Heap, Event Loop, and why Promises always beat setTimeout—with visual diagrams and real-world patterns.

Dec 28, 202528 min read
JavaScriptEvent LoopAsync ProgrammingPerformanceInterview Prep

Understanding the JavaScript Runtime is the key to writing performant, non-blocking code. It explains why setTimeout(..., 0) doesn't execute immediately, why your UI freezes during heavy computation, and how asynchronous code actually works under the hood.

Here is your complete guide to the JavaScript Runtime.


1. The Big Picture: What is the JavaScript Runtime?

The JavaScript Runtime is the complete environment that executes your JavaScript code. It's not just the language itself—it's a combination of several components working together.

Think of it like a restaurant kitchen:

  • Call Stack = The chef (can only cook one dish at a time)
  • Web APIs = Kitchen assistants (handle timers, orders from customers)
  • Callback Queue = Dishes ready to be served (waiting in line)
  • Event Loop = The head waiter (checks if chef is free, then brings next dish)

The Core Components

ComponentWhat It DoesReal-World Analogy
Call StackExecutes functions one at a time, LIFO orderChef cooking dishes in order
Memory HeapStores objects, arrays, functionsIngredient storage room
Web APIsHandle async operations (timers, HTTP, DOM)Kitchen helpers doing background tasks
Callback QueueHolds completed async callbacks waiting to runServed dishes waiting to go out
Microtask QueueHigh-priority callbacks (Promises)VIP orders that skip the line
Event LoopMoves callbacks to stack when it's emptyManager coordinating everything

How They Connect

The Flow:

  1. Your Code Runs → Goes to Call Stack
  2. Async Call (setTimeout, fetch) → Sent to Web API
  3. Web API Completes → Callback goes to Queue
  4. Event Loop Checks → Is Call Stack empty?
  5. If Empty → Move callback from Queue to Stack
  6. Execute Callback → Back to step 1

2. The Call Stack: JavaScript's Single Thread

JavaScript is single-threaded—it has only ONE call stack. This means it can only do ONE thing at a time.

How the Call Stack Works

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);

Step-by-step execution:

StepActionCall Stack (bottom to top)
1Call printSquare(4)printSquare
2Call square(4)printSquare → square
3Call multiply(4, 4)printSquare → square → multiply
4multiply returns 16printSquare → square
5square returns 16printSquare
6console.log(16)printSquare → console.log
7printSquare completes(empty)

Key Insight: Functions stack on top like plates. The last one added is the first one removed (LIFO - Last In, First Out).

Stack Overflow: When Things Go Wrong

function infinite() {
    infinite(); // Calls itself forever
}

infinite();
// ❌ Uncaught RangeError: Maximum call stack size exceeded

The stack has a maximum size (~10,000-50,000 frames). Exceed it, and you get the infamous stack overflow error.


3. The Memory Heap: Where Data Lives

The Memory Heap is where JavaScript stores objects, arrays, and functions. Unlike the stack (which is ordered), the heap is unstructured memory.

// Primitives - stored directly in stack
const age = 25;           // Value: 25
const name = "John";      // Value: "John"

// Objects - reference in stack, data in heap
const user = {
    name: "John",
    age: 25,
    hobbies: ["reading", "coding"]
};

Memory Storage Breakdown

TypeWhere StoredHow It Works
Primitives (number, string, boolean, null, undefined)Call StackDirect value storage
ObjectsMemory HeapStack holds reference (memory address)
ArraysMemory HeapStack holds reference
FunctionsMemory HeapStack holds reference

Memory Tip: When you assign an object to a new variable, you're copying the reference, not the object itself. That's why modifying one affects the other.


4. Web APIs: The Complete Reference

JavaScript itself doesn't have timers, network capabilities, or DOM access. These are provided by the Web APIs (in browsers) or C++ APIs (in Node.js).

Example: How setTimeout Works Behind the Scenes

console.log("Start");

setTimeout(() => {
    console.log("Timer callback");
}, 2000);

console.log("End");

// Output:
// Start
// End
// Timer callback (after 2 seconds)

Step-by-step breakdown:

StepCall StackWeb APICallback QueueOutput
1console.log("Start")--"Start"
2setTimeout(...)Timer starts (2s countdown)--
3console.log("End")Timer running...-"End"
4(empty)Timer completes!callback-
5callback (moved by Event Loop)-(empty)"Timer callback"

5. Complete Web APIs Reference

Here is a comprehensive categorization of all major Web APIs:

🕐 Timing APIs

APIPurposeGoes To Queue
setTimeout(fn, delay)Execute once after delayMacrotask Queue
setInterval(fn, delay)Execute repeatedlyMacrotask Queue
clearTimeout(id)Cancel a setTimeoutSynchronous
clearInterval(id)Cancel a setIntervalSynchronous
requestAnimationFrame(fn)Execute before next paint (~60fps)Animation Frame Queue
cancelAnimationFrame(id)Cancel animation frameSynchronous
requestIdleCallback(fn)Execute when browser is idleIdle Queue

🌐 Network APIs

APIPurposeGoes To Queue
fetch(url, options)Modern HTTP requestsPromise → Microtask Queue
XMLHttpRequestLegacy HTTP requestsMacrotask Queue
WebSocketReal-time bidirectional communicationMacrotask Queue
EventSourceServer-Sent EventsMacrotask Queue
navigator.sendBeacon()Send data on page unloadAsync (fire-and-forget)

📄 DOM APIs

APIPurposeGoes To Queue
addEventListener(event, fn)Listen for user eventsMacrotask Queue
removeEventListener(event, fn)Remove event listenerSynchronous
MutationObserverWatch for DOM changesMicrotask Queue
IntersectionObserverDetect element visibilityMacrotask Queue
ResizeObserverDetect element size changesMacrotask Queue

💾 Storage APIs

APIPurposeGoes To Queue
localStoragePersistent key-value storageSynchronous (blocking!)
sessionStorageSession-only key-value storageSynchronous (blocking!)
IndexedDBLarge structured data storageMacrotask Queue
Cache APICache network responsesPromise → Microtask Queue
CookiesSmall data with expirySynchronous

📍 Device APIs

APIPurposeGoes To Queue
navigator.geolocationGet user locationMacrotask Queue
navigator.clipboardRead/write clipboardPromise → Microtask Queue
navigator.mediaDevicesAccess camera/microphonePromise → Microtask Queue
Notification APIShow system notificationsMacrotask Queue
Vibration APIVibrate deviceSynchronous
Battery Status APIGet battery infoPromise → Microtask Queue

🔧 Worker APIs (True Parallelism!)

APIPurposeGoes To Queue
Web WorkersRun JS in background threadMessage → Macrotask Queue
Service WorkersProxy network requests, offline supportMessage → Macrotask Queue
Shared WorkersShared state across tabsMessage → Macrotask Queue

🎨 Graphics & Media APIs

APIPurposeGoes To Queue
Canvas API2D drawingSynchronous
WebGL3D graphicsSynchronous
Web Audio APIAudio processingMacrotask Queue
MediaRecorderRecord audio/videoMacrotask Queue

6. The Event Loop: The Heart of Async JavaScript

The Event Loop is the mechanism that allows JavaScript to perform non-blocking operations despite being single-threaded.

The Event Loop Algorithm (Simplified)

// This is what the browser does internally
while (true) {
    // Step 1: Run all synchronous code until stack is empty
    
    // Step 2: Process ALL microtasks
    while (microtaskQueue.length > 0) {
        runNextMicrotask();
    }
    
    // Step 3: Process ONE macrotask
    if (macrotaskQueue.length > 0) {
        runNextMacrotask();
    }
    
    // Step 4: Render if it's time (~16ms for 60fps)
    if (shouldRender()) {
        runAnimationFrameCallbacks();
        render();
    }
    
    // Repeat forever...
}

The Complete Execution Priority

PriorityQueue/PhaseExamplesHow Many Run?
1 (Highest)Synchronous CodeRegular JS codeAll of it
2Microtask QueuePromise.then(), queueMicrotask(), MutationObserverALL until empty
3Macrotask QueuesetTimeout, setInterval, DOM events, I/OONE task
4RenderStyle calculation, layout, paintIf needed
5Animation FramesrequestAnimationFrameAll callbacks
Repeat from 2

Critical Rule: After every macrotask, ALL microtasks are executed before the next macrotask or render.


7. Microtasks vs Macrotasks: The Priority Battle

Not all async callbacks are equal. JavaScript has TWO main queues with different priorities.

Quick Reference

Microtask Queue (VIP) 🏆Macrotask Queue (Regular) 📋
Promise.then() / .catch() / .finally()setTimeout()
queueMicrotask()setInterval()
MutationObserverDOM Events (click, keyup, etc.)
async/await (after await)XMLHttpRequest callbacks
setImmediate() (Node.js)
I/O operations
MessageChannel

Example: The Order Puzzle

console.log("1: Script start");

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

Promise.resolve()
    .then(() => console.log("3: Promise 1"))
    .then(() => console.log("4: Promise 2"));

console.log("5: Script end");

Output:

1: Script start
5: Script end
3: Promise 1
4: Promise 2
2: setTimeout

Step-by-Step Breakdown

StepWhat HappensCall StackMicrotask QueueMacrotask Queue
1Run sync: log "1"console.log--
2Register setTimeout--callback
3Create Promise, register .then-then1callback
4Run sync: log "5"console.logthen1callback
5Stack empty → Run microtasksthen1 logs "3"then2callback
6Continue microtasksthen2 logs "4"(empty)callback
7Run macrotaskcallback logs "2"-(empty)

8. Common Interview Trap: setTimeout(..., 0)

One of the most misunderstood concepts is setTimeout with a delay of 0.

setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("sync");

// Output:
// sync
// promise
// timeout

Why Doesn't setTimeout(0) Run Immediately?

ReasonExplanation
It's still asyncGoes to Web API → Macrotask Queue → waits for stack
Microtasks have priorityPromises ALWAYS run before timeouts
Minimum delayBrowsers enforce ~4ms minimum for nested timeouts
Must wait for stackEven 0ms delay means "as soon as possible, not now"

The "Zero" Delay Myth

const start = Date.now();

setTimeout(() => {
    console.log(`Actual delay: ${Date.now() - start}ms`);
}, 0);

// Actual delay: 1-4ms (not 0!)

9. Blocking the Event Loop: The Performance Killer

Since JavaScript is single-threaded, any long-running synchronous code blocks everything—including UI updates, user interactions, and other callbacks.

❌ Bad: Blocking Code

// This freezes the entire page!
function heavyComputation() {
    for (let i = 0; i < 1_000_000_000; i++) {
        // Expensive calculation
    }
}

button.addEventListener('click', () => {
    heavyComputation(); // UI is frozen for seconds!
    updateUI();         // User sees nothing until this finishes
});

✅ Good: Breaking Up Work (Time-Slicing)

function processChunk(data, index, callback) {
    const chunkSize = 1000;
    const end = Math.min(index + chunkSize, data.length);
    
    for (let i = index; i < end; i++) {
        // Process item
    }
    
    if (end < data.length) {
        // Schedule next chunk, allowing UI to update between chunks
        setTimeout(() => processChunk(data, end, callback), 0);
    } else {
        callback(); // Done!
    }
}

✅ Best: Web Workers (True Parallelism)

// main.js - UI thread stays responsive!
const worker = new Worker('heavy-computation.js');

worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
    console.log('Result:', e.data);
    updateUI(e.data);
};

// heavy-computation.js - runs in separate thread
self.onmessage = (e) => {
    const result = heavyComputation(e.data);
    self.postMessage(result);
};

10. Real-World Patterns

Pattern 1: Debouncing with setTimeout

function debounce(fn, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
}

// Usage: Only fires after user stops typing for 300ms
input.addEventListener('input', debounce(handleSearch, 300));

Pattern 2: Throttling with setTimeout

function throttle(fn, limit) {
    let inThrottle = false;
    return function(...args) {
        if (!inThrottle) {
            fn.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// Usage: Fire at most once every 100ms
window.addEventListener('scroll', throttle(handleScroll, 100));

Pattern 3: Forcing a Repaint

// Force browser to apply changes before animation
element.style.transform = 'translateX(0)';

// Force a reflow (trigger layout calculation)
element.offsetHeight;

// Now animate (browser has the starting point)
element.style.transition = 'transform 0.3s';
element.style.transform = 'translateX(100px)';

Pattern 4: queueMicrotask for High-Priority Async

// When you need something async but BEFORE any setTimeout
queueMicrotask(() => {
    console.log("Runs after current sync code, before any timers");
});

Pattern 5: Promise.resolve() for Deferring

// Defer execution but with high priority (microtask)
Promise.resolve().then(() => {
    console.log("Runs after current sync, as a microtask");
});

11. The Complete Execution Order

Here's the definitive ordering of JavaScript execution:

OrderPhaseWhat Runs
1Synchronous CodeAll regular JS until stack is empty
2Microtasks (ALL)Every Promise.then(), queueMicrotask()
3One MacrotaskOne setTimeout, event callback, etc.
4Microtasks (ALL)Any new microtasks from step 3
5Render (if needed)Style calc, layout, paint
6requestAnimationFrameAnimation callbacks
7Go to Step 3Repeat the loop

The Ultimate Test

console.log("1");

setTimeout(() => console.log("2"), 0);

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

queueMicrotask(() => console.log("5"));

console.log("6");

// Output: 1, 6, 3, 5, 4, 2

Breakdown:

  1. Sync: 1, 6 (run immediately)
  2. Microtasks: 3 (first .then), 5 (queueMicrotask), 4 (chained .then)
  3. Macrotask: 2 (setTimeout)

12. Async/Await Under the Hood

async/await looks synchronous but works through Promises and microtasks. Understanding this is crucial for interviews.

How Await Actually Works

async function fetchData() {
    console.log('1: Before await');
    const data = await fetch('/api/data');  // Pause here
    console.log('2: After await');
    return data;
}

console.log('3: Start');
fetchData();
console.log('4: End');

// Output: 3, 1, 4, 2

What happens at await:

  1. Before await — Code runs synchronously (console.log('1'))
  2. At await — Function pauses, rest becomes a microtask
  3. Control returns — To the caller, sync code continues (console.log('4'))
  4. Promise resolves — Microtask executes (console.log('2'))

The Secret: await = .then()

These are equivalent:

// async/await version
async function foo() {
    const x = await Promise.resolve(42);
    console.log(x);
}

// Promise version (what the engine actually does)
function foo() {
    return Promise.resolve(42).then(x => {
        console.log(x);
    });
}

Tricky Interview Question

async function async1() {
    console.log('1');
    await async2();
    console.log('2');
}

async function async2() {
    console.log('3');
}

console.log('4');
async1();
console.log('5');

// Output: 4, 1, 3, 5, 2

Step-by-step:

StepWhat HappensOutput
1Sync: log '4'4
2Call async1(), log '1'4, 1
3Call async2(), log '3'4, 1, 3
4await pauses async1, schedule rest as microtask-
5Sync continues: log '5'4, 1, 3, 5
6Microtask: log '2'4, 1, 3, 5, 2

13. Interview Tips & Common Pitfalls

🎯 Common Interview Questions

  1. "Explain the Event Loop" — Mention: single-threaded, call stack, Web APIs, task queues, and how the loop moves callbacks
  2. "Microtasks vs Macrotasks" — Microtasks (Promises) have higher priority, ALL run before next macrotask
  3. "Why does setTimeout(0) not run immediately?" — Goes through Web API → macrotask queue → waits for stack + microtasks
  4. "What happens at await?" — Function pauses, rest becomes microtask, control returns to caller

⚠️ Common Pitfalls

PitfallWhy It HappensSolution
Assuming setTimeout(0) is instantStill goes through the entire async pipelineUse queueMicrotask() for immediate async
Infinite microtask loopsMicrotasks adding microtasks block the event loopBreak work into chunks with setTimeout
Blocking the main threadLong sync operations freeze everythingUse Web Workers for heavy computation
Race conditionsAsync operations complete in unexpected orderUse Promise.all() or careful sequencing
Not handling Promise rejectionsUnhandled rejections cause silent failuresAlways use .catch() or try/catch

💡 Pro Tips for Interviews

  1. Draw it out — Sketch the call stack, queues, and Web APIs when explaining
  2. Trace execution order — Walk through code step-by-step, saying "sync first, then microtasks, then macrotasks"
  3. Know the exceptions — queueMicrotask is a pure microtask (no Promise overhead)
  4. Mention browsers vs Node.js — Node has process.nextTick (before microtasks!) and setImmediate
  5. Discuss real-world impact — Relate to UI freezing, race conditions, performance optimization

Node.js Differences

BrowserNode.js
No process.nextTickprocess.nextTick runs BEFORE microtasks
No setImmediatesetImmediate runs after I/O callbacks
Uses Web APIsUses libuv C++ library
DOM eventsNo DOM, but file I/O, network
// Node.js specific order
process.nextTick(() => console.log('nextTick'));  // 1st
Promise.resolve().then(() => console.log('promise')); // 2nd
setImmediate(() => console.log('immediate'));     // 3rd (after I/O)
setTimeout(() => console.log('timeout'), 0);      // 3rd-ish

Key Takeaways

ConceptWhat to Remember
Single-ThreadedJavaScript has ONE call stack—can only do one thing at a time
Web APIs are ExternalTimers, fetch, DOM events are NOT part of JS—they're browser features
Event Loop = CoordinatorMoves callbacks from queues to stack when stack is empty
Microtasks > MacrotasksPromises ALWAYS run before setTimeout, setInterval
await = .then()async/await is syntactic sugar; await schedules a microtask
Don't Block the ThreadLong sync operations freeze the UI completely
Use Workers for Heavy WorkWeb Workers give you true parallelism
setTimeout(0) ≠ ImmediateIt means "as soon as possible" not "right now"

Senior Engineer Tip: Understanding the runtime isn't just academic—it's essential for debugging race conditions, optimizing performance, and writing predictable async code. The next time your code behaves unexpectedly, visualize the event loop and trace through the queues.

🧠 Test Your Knowledge

Now that you've learned the concepts, let's see if you can apply them! Take this quick quiz to test your understanding.

Written by

Shaik Munsif

Read more articles

Found this helpful? Share it with your network!

Question 1 of 20Easy
Score: 0/0

JavaScript is described as single-threaded because it has: