One loop, many duties
The browser runs a single event loop on the main thread. Each turn it may run a task, drain microtasks, and possibly render a frame.
The order within a turn
- Run one task from the task queue, such as a click handler or a timer callback.
- Drain the entire microtask queue, including promise callbacks.
- If it is time to paint, run the rendering steps: requestAnimationFrame callbacks, style, layout, paint, and composite.
Why rendering can be starved
Rendering only happens between tasks. A long running task or an endless chain of microtasks blocks rendering, so the page freezes and input feels unresponsive.
Keeping the page smooth
- Break heavy work into smaller tasks so the loop can render in between.
- Move animation updates into requestAnimationFrame, which the loop runs at paint time.
- Offload pure computation to a worker to keep the main thread free.
Mental model
Think of each loop turn as: do a task, settle all promises, then maybe paint. Anything that never yields the thread denies the browser its chance to render.
Key idea
The event loop runs a task, drains microtasks, then maybe renders; work that never yields the main thread starves rendering.