Event Loopcritical

Event Loop Tick & Ordering

One event loop 'tick' is: execute one macrotask (or the initial script), then drain all microtasks, then optionally render, then pick the next macrotask.

Memory anchor

One event loop tick is like one spin of a washing machine: wash one load (macrotask), rinse ALL the soap out (drain microtasks), optionally hang to dry (render), then grab the next load.

Expected depth

The ordering of async operations is: synchronous code → microtasks (Promise.then, queueMicrotask) → macrotasks (setTimeout, I/O). A common interview question is predicting the output of interleaved Promises and setTimeouts. Promise.resolve().then(A) will always run A before setTimeout(B, 0) runs B, regardless of which was registered first.

Deep — senior internals

Between the microtask checkpoint and the next macrotask, browsers may run a rendering step — but only if there is a pending visual update and enough time has passed (~16ms at 60fps). requestAnimationFrame callbacks run as part of the rendering step, not as macrotasks or microtasks. This means rAF fires after microtasks but before the next setTimeout. The rendering step is skippable — if the browser determines no visual update is needed, it jumps to the next macrotask.

🎤Interview-ready answer

The canonical event loop order is: run current task → drain microtask queue → (browser: rendering step if needed) → pick next macrotask. This means Promise chains always complete before setTimeout(fn, 0) fires. requestAnimationFrame sits between microtasks and the next macrotask in browsers, making it the right tool for synchronizing with the rendering pipeline.

Common trap

requestAnimationFrame is not a macrotask and not a microtask — it runs as part of the browser's rendering step. Putting animation logic in setTimeout is wrong because it's not synchronized with the display refresh rate, causing jank. Putting it in a Promise chain is wrong because microtasks block rendering.

Related concepts