Where in a browser's event-loop turn does a requestAnimationFrame callback run relative to the task that scheduled it and the paint that follows?
answer
- not inside the calling task
- the browser's rendering update
- after animations, before style and layout
- last hook before pixels
- the list is emptied before it runs
basics
~20 sAn animation frame callback runs during the browser's rendering update: after the task that registered it has finished and its microtasks have drained, and before style recalculation, layout and paint for that frame. It is the last hook before pixels.
solid answer
~50 sThe callback does not run inside the task that called `requestAnimationFrame`. That task finishes first, its microtasks drain, and only then may the event loop decide this is a rendering opportunity — which it takes at most once per display frame, and skips entirely for a page that is not being rendered. Inside that rendering update the browser runs the resize and scroll steps, evaluates media queries, updates animations, and then invokes the animation frame callbacks. Style recalculation, layout, paint and compositing happen after your callback returns, so anything you write to the DOM there lands in the very frame the user is about to see. That is the whole point of the hook: it is the latest place to mutate before pixels, and the earliest place where you can be sure the previous frame is already on screen.
go deeper
Know the ordering in plain terms: the current code finishes, then the browser gets to the frame, then your callback runs, then the page is painted. Do not describe the callback as running immediately.
Be able to place the callback inside the rendering update — after animation updates, before style recalculation, layout and paint — and to explain that rendering happens at most once per display frame, not on every turn of the loop.
Reason about consequences: the whole frame budget covers your callback plus style, layout, paint and every other frame-tied callback, and microtasks produced inside the callback still delay the paint. Explain why a rAF callback is not insulated from a long task elsewhere.
Own the convention for where per-frame work is allowed to live across a codebase, and be able to argue which work belongs in the pre-paint hook at all versus being moved off the frame path entirely.
## The turn, not the call Calling `requestAnimationFrame(cb)` does nothing except append `cb` to the document's list of animation frame callbacks and hand you a handle. The currently running task carries on to completion — the browser will not interrupt it. Only once that task is finished, and the microtask work it queued has drained, does the event loop get to decide what to do next. One of the things it can decide is to *update the rendering*. It does not do this on every turn. Rendering is throttled to the display: at most one update per refresh interval, and none at all for a document that is not currently being rendered, such as a page in a background tab. So there is no fixed number of tasks between your call and your callback — there may be many turns with no frame in between, or the frame may come almost immediately. ## What the rendering update runs, in order When the browser does take a rendering opportunity, it works through a defined sequence for each document it is about to render. In order, the interesting parts are: 1. run the resize steps (fire `resize`), 2. run the scroll steps (fire `scroll`), 3. evaluate media queries and report changes, 4. update animations and send their events (CSS animations, transitions, Web Animations), 5. **run the animation frame callbacks** — this is your `rAF` callback, 6. deliver observer callbacks that are tied to the rendering update, notably resize and intersection observations, 7. recalculate style, run layout, paint, and hand off to the compositor. Two things follow. Your callback runs *after* the browser has advanced declarative animations for this frame, so the values it reads reflect this frame's animation state. And it runs *before* style and layout for this frame, so a DOM write inside it is picked up by the very next steps rather than waiting a frame. ## The callback list is captured, then cleared Before invoking the callbacks, the browser takes the current list and empties it. Every callback registered *before* this frame started runs now, in registration order. Every callback registered *during* the run — including the `requestAnimationFrame` you call at the end of your own callback to keep an animation going — goes onto the fresh list and runs at the **next** frame, not later in this one. This is what makes the self-rescheduling loop idiom terminate cleanly at one callback per frame instead of spinning forever inside a single frame: ```js requestAnimationFrame(function loop() { requestAnimationFrame(loop); // next frame, never this one }); ``` ## Microtasks queued inside the callback A promise reaction or `queueMicrotask` scheduled inside an animation frame callback does not wait for the next frame. The stack empties between callback invocations, so those microtasks run before the browser moves on — and certainly before style, layout and paint. Practically: resolving a promise inside a rAF callback and writing to the DOM from its `.then` still lands in the same frame. That is convenient, and it is also a way to hang the frame, because the browser cannot start painting while microtasks are still being produced. ## Why the position matters in practice Because the callback is the last hook before rendering, it is where you put writes whose effect must be visible this frame. Because it is the first place after the previous frame has been handed off, it is also a natural point at which measurements reflect the state the user is currently looking at. The other half of the lesson is what the position does *not* give you. Your callback is not on a separate thread and gets no special priority: it is scheduled by the same event loop as everything else, so a long task that overruns the frame deadline delays the whole rendering update, callback included. And the frame's budget covers everything in steps 1 through 7, not just your code — you are sharing roughly 16.7 ms on a 60 Hz display with style, layout, paint and every other callback registered for the same frame. ## A frequent misconception "rAF runs before the next repaint" is often repeated as "rAF runs immediately". It does not. If you register a callback and then synchronously change the DOM, the change and the callback are not in the same moment — the change is already applied to the DOM tree, and the callback comes later, once the browser gets around to producing a frame. If no frame is produced, for example because the page is hidden, the callback never runs at all.
- Inside an animation frame callback you call requestAnimationFrame again. When does that second callback run?At the next frame. The browser copies and clears the callback list before invoking anything, so registrations made during the run land on a fresh list. That is exactly what makes the self-rescheduling loop produce one callback per frame instead of looping forever inside one frame.
- If a rAF callback resolves a promise, does the .then handler run before or after the frame is painted?Before. The microtask checkpoint is reached between callback invocations, well ahead of style, layout and paint, so a DOM write from that handler still lands in the same frame. It also means a flood of microtasks generated inside a callback delays the paint it was supposed to precede.
- Is there any guaranteed number of tasks between calling requestAnimationFrame and the callback firing?No. The callback fires at the next rendering opportunity, and the event loop takes those at most once per display frame and skips them for documents not being rendered. Many tasks may run in between, or almost none, and for a hidden page the callback may never run.
saying these in an interview costs you the question
- Says the callback runs immediately or synchronously
- Thinks rAF callbacks run after paint rather than before
- Believes a rAF registered inside a callback runs in the same frame
- Assumes a rendering update happens on every event-loop turn
- Claims rAF callbacks get their own thread or priority