← Back to all publications

Demystifying the JavaScript Runtime: Event Loops, Microtasks, and Call Stacks

Gain complete intuition over asynchronous JavaScript. Understand execution phases between Macrotask queues, Microtask queues, requestAnimationFrame, and render steps.

1. CALL STACK (LIFO) console.log('1') executeSync() main() frame Drains synchronously EVENT LOOP Pushes 2. MICROTASKS (DRAINED TO COMPLETION) Promise.then() • queueMicrotask Drains completely before any render or macrotask 3. MACROTASKS (ONE PER TICK) setTimeout • setInterval • I/O callback 1 task executed per tick after microtask drain Execution Sequence: Synchronous Script → Microtask Drain → Render → Macrotask Guarantees deterministic ordering: console.log('1') → 5 → 3 → 4 → 2

The Single-Threaded Deception

JavaScript executes in a single-threaded runtime model, meaning it has one call stack and one memory heap. However, the runtime host environment (Node.js, V8 in Chrome, or workerd in Cloudflare Workers) exposes asynchronous Web APIs backed by multi-threaded thread pools.

Microtasks vs. Macrotasks Execution Order

Consider this standard interview question:

console.log('1');

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

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

console.log('5');

The output is strictly 1, 5, 3, 4, 2. Why?

  • console.log('1') executes synchronously.
  • setTimeout schedules a callback in the Macrotask Queue (timer phase).
  • Promise.resolve() queues its callbacks into the Microtask Queue.
  • console.log('5') executes synchronously.
  • Before the event loop picks the next macrotask, it drains the entire microtask queue to completion!
HB

Written by HB

Writing on systems programming, backend architectures, and modern web engineering.

Continue Reading

Cloud Infrastructure

Architecting Distributed State at the Edge with Durable Objects

2026-09-01 • 7 min read
TypeScript

Mastering TypeScript Generics: Patterns for Robust, Type-Safe Systems

2026-09-05 • 6 min read