JavaScript is single-threaded, yet handles thousands of concurrent operations (network requests, timers, UI events) without blocking — the event loop is the mechanism that makes this possible.
console.log('1: sync');
setTimeout(() => console.log('2: macrotask (setTimeout)'), 0);
Promise.resolve().then(() => console.log('3: microtask (promise)'));
console.log('4: sync');
// Actual output order: 1, 4, 3, 2
// Sync code runs first, THEN all microtasks (promises),
// THEN macrotasks (setTimeout) - even with a 0ms delay
The Core Model
The call stack runs synchronous code to completion first. Once it’s empty, the event loop drains the entire microtask queue (Promises, queueMicrotask) before processing even a single macrotask (setTimeout, I/O events) — this ordering is exactly why a setTimeout(fn, 0) never actually runs immediately, even though the delay is zero.