Mental model
JavaScript event loops maintain two distinct queues: the Macrotask Queue (setTimeout, setInterval, I/O) and the Microtask Queue (Promise.then, queueMicrotask, process.nextTick).
Theory
After every synchronous call stack frame empties, the event loop drains the entire Microtask Queue to completion before processing the next macrotask or performing DOM layout repaints. An infinite loop of microtasks (Promise.resolve().then(...)) starves the macrotask queue and freezes browser UI rendering.
Alternatives and trade-offs
setTimeout(fn, 0) schedules execution in the next macrotask tick. queueMicrotask(fn) schedules immediate execution after the current synchronous stack frame without UI rendering delay.
Failure modes and misconceptions
- Async/Await is not multithreaded:
asyncfunctions do not run on background CPU threads; they execute on the main event loop. - Microtask Starvation: Infinite microtask loops block the event loop permanently, preventing UI events or network callbacks from firing.
Decision scenario
When breaking up long-running UI computations, yield using setTimeout or requestAnimationFrame so the browser can process user inputs and render frames between chunks.
Learning outcomes
- Compare microtask queue priority against macrotask processing.
- Explain execution order across Promises, async/await, and timers.
- Prevent microtask queue starvation in high-throughput applications.
Trade-offs
Microtasks execute immediately after synchronous code without render delays, but un-bounded microtask recursion will freeze user interface interactions.