The Event Loop's Hidden Half: Rendering, Microtask Stalls, and the 16.6ms Budget
Real performance problems like dropped frames, layout thrashing, and frozen pages stem from how code interacts with the rendering pipeline, not from the classic microtask-versus-macrotask ordering. Knowing the full frame sequence lets a developer fix those problems directly: keep microtask chains short, use rAF for animations, and batch DOM reads and writes to avoid forced reflows.
The browser event loop is usually taught with a one-line quiz: setTimeout logs after Promise.resolve. That model covers task scheduling but ignores the rendering pipeline, which is where real performance problems live. A complete walkthrough of a 16.6ms frame lays out the sequence: one macro task, a full drain of microtasks, a render decision, requestAnimationFrame callbacks, the Style/Layout/Paint/Composite pipeline, and finally requestIdleCallback if budget remains. Each step carries concrete consequences: recursive microtasks can starve rendering and freeze the page; continuous DOM writes in one task batch into a single render; reading layout offsets forces a synchronous reflow; and requestIdleCallback runs after paint, so mutating DOM there delays visible updates by a frame.
The same walkthrough explains why requestAnimationFrame is the only reliable animation hook: it runs before style and layout, so changes from its callback appear in the same frame. setTimeout, by contrast, lands in the macro task queue and may miss the current frame entirely. The layout thrashing example shows that interleaving reads and writes forces repeated Layout passes, while batching reads then writes cuts the work by orders of magnitude. A deliberately tricky interview problem ties it together, with an output order that depends on whether the browser decides to render that loop.
The takeaway is a mental model of the event loop that matches what the browser actually does each frame, not just what prints to the console. Knowing the order of console.logs is far less valuable than knowing when the browser paints, and where your code inserts unnecessary computation into that pipeline.
The interview-quiz model of the event loop leaves out the rendering pipeline entirely, yet most real performance issues come from that pipeline, not from whether Promise callbacks beat setTimeouts.
A microtask chain that never yields can starve rendering and freeze a page; the same logic with setTimeout keeps painting alive, making the choice of scheduling primitive a genuine performance decision.
Separating DOM reads from writes is a broad, reusable performance rule that falls directly out of how forced reflows work; it fixes layout thrashing without micro-optimization.