Understanding JavaScript's Event Loop: Microtasks vs Macrotasks

5/29/2026Forgeora Developer
Understanding JavaScript's Event Loop: Microtasks vs Macrotasks

Why does a resolved Promise run before a setTimeout(..., 0)? The answer lies in the event loop's distinction between microtasks and macrotasks. Master this and async JavaScript will make sense.

# Understanding JavaScript's Event Loop: Microtasks vs Macrotasks JavaScript is single-threaded. Yet we write async code all the time. The event loop is the mechanism that makes this work—and understanding the microtask/macrotask distinction explains a lot of subtle behavior. ## The Event Loop in Brief 1. Execute the current call stack until empty. 2. Drain the **microtask queue** completely. 3. Pick one task from the **macrotask queue** and execute it. 4. Repeat. ## Microtasks vs Macrotasks | Type | Examples | |---|---| | Microtask | `Promise.then`, `queueMicrotask`, `MutationObserver` | | Macrotask | `setTimeout`, `setInterval`, `setImmediate`, I/O callbacks | ## The Classic Puzzle ```javascript console.log("1"); setTimeout(() => console.log("2"), 0); Promise.resolve().then(() => console.log("3")); console.log("4"); // Output: 1, 4, 3, 2 ``` Why? After the synchronous code (1, 4), the microtask queue (Promise → 3) is fully drained before the macrotask queue (setTimeout → 2) is touched. ## Practical Implications **Don't block the microtask queue:** ```javascript // This starves the macrotask queue (and blocks rendering) function infiniteMicrotasks() { Promise.resolve().then(infiniteMicrotasks); } ``` **Use `queueMicrotask` for fine-grained scheduling:** ```javascript queueMicrotask(() => { // Runs before the next macrotask but after the current task }); ``` **`async/await` is syntactic sugar over Promises:** ```javascript async function run() { await Promise.resolve(); console.log("After await"); // microtask } ``` Understanding the event loop turns mysterious async bugs into predictable, debuggable behavior.