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.
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.
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.
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.
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.
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.