Engrain by ByteRush

JS.01.04 Mid-level· 10 minCore — asked often

Rendering steps: where requestAnimationFrame fits in the event loop

The browser paints between tasks, at the screen's refresh rate. requestAnimationFrame runs your code right before that paint — the right place for animation.

Comes up in: JavaScript, Web fundamentals

Learn#

Sana animates a progress bar with setTimeout(step, 16). On her laptop it looks fine. On a 120 Hz phone it stutters, and on a busy page it jumps. Her timer does not know when the screen is about to be drawn.

Between tasks, the event loop sometimes runs an extra step: update the rendering. This is when a frame is drawn.

requestAnimationFrame(callback) — rAF for short — asks the browser to run your callback in that step, right before it works out styles, layout and paint. Changes you make there appear in the very next frame.

Frames follow the screen: about every 16.7 ms at 60 Hz, 8.3 ms at 120 Hz. rAF matches the screen automatically, so the same code is smooth on both.

Not every turn of the loop draws a frame. If nothing changed, or the screen is not ready, the browser skips that rendering opportunity.

function animate(now) {
  const progress = Math.min(1, (now - start) / 600); // time-based, not frame-based
  bar.style.transform = `scaleX(${progress})`;
  if (progress < 1) requestAnimationFrame(animate);
}
const start = performance.now();
requestAnimationFrame(animate);
Task, microtasks, then a frameStep 1 of 5

Call stack

  • click handler

Microtasks

    empty

    rAF callbacks

    • move box

    Console

      empty

      A task runs: your click handler. It changes a box's position and asks for an animation frame.

      Read the steps as text
      1. A task runs: your click handler. It changes a box's position and asks for an animation frame.
      2. The task ends. All microtasks run first, as always.
      3. It is time for a frame. The browser runs every requestAnimationFrame callback.
      4. Then it works out styles and layout, and paints. You see the box move.
      5. Back to tasks. A timer that became ready meanwhile runs now.

      Predict#

      Predict first

      In a browser: setTimeout(() => log('timeout')); requestAnimationFrame(() => log('raf')); Promise.resolve().then(() => log('promise')); log('sync'). What is guaranteed?

      Interview angle#

      Say it like this — about 30 seconds

      “rAF callbacks run in the event loop’s rendering step: after a task and its microtasks, right before the browser computes style and layout and paints. The browser runs that step about once per screen refresh — 60 or 120 times a second — and may skip it if nothing needs drawing. So rAF is the right place for visual updates, it adapts to the refresh rate, and it pauses in background tabs. Its order relative to a timer is not fixed.”

      Stays on your device. Nothing is uploaded.
      Follow-up Why is rAF better than setTimeout for animation?
      It runs right before paint at the screen’s rate, so you never draw twice per frame or miss one, and it pauses in hidden tabs to save battery.
      Follow-up What is requestIdleCallback for?
      Low-priority work, run when the browser has spare time in a frame. It is not in Safari by default, so feature-detect it.
      Follow-up Why use the timestamp passed to the rAF callback?
      To move by elapsed time, not by frame count. Then the animation takes the same time at 60 Hz, 120 Hz or when frames drop.

      Practice#

      On your own

      Write tween(from, to, duration, onUpdate, raf). Base each value on elapsed time from the frame timestamps, not on how many frames have passed. The tests pass a fake raf at 60 Hz and at 120 Hz.

      Done when

      • The last value is exactly to, and the promise resolves after it.
      • The animation takes duration ms at both 60 Hz and 120 Hz.
      • Values move steadily from from toward to, counting down too.
      Your solution
      Ctrl/⌘ + Enter

      Remember#

      Try to answer each card before you open it. Then add them to your reviews.

      When do requestAnimationFrame callbacks run?

      In the rendering step, after tasks and microtasks, right before style, layout and paint.

      How often does the browser render?

      Up to once per screen refresh (about 60 or 120 times a second), skipping frames with nothing to draw.

      Should an animation move by frames or by time?

      By elapsed time, using the rAF timestamp.

      What happens to rAF in a background tab?

      Most browsers pause it until the tab is visible again.

      Go deeper

      Four layers. Stop at the one your interview needs; each ends with a quick check.

      The core ideaEvery round

      Tasks, then microtasks, then — when the screen is ready — a frame. rAF lets you run code at the start of that frame. Visual changes belong there.

      Check yourself: Is a DOM change drawn the moment you make it?

      No. It is drawn at the next rendering step, after the current task and microtasks.

      How it works underneathSenior rounds

      The HTML Standard’s “update the rendering” step runs resize and scroll steps, then animation frame callbacks, then style, layout and paint Source: HTML Standard: Update the rendering. Each callback receives the same timestamp for that frame, so several animations stay in step Source: MDN: Window.requestAnimationFrame().

      Check yourself: What runs first in the rendering step: rAF callbacks or layout?

      rAF callbacks, then style and layout, then paint.

      Edge cases and trapsWhere people slip

      A rAF scheduled inside a rAF callback waits for the next frame. Most browsers pause rAF in background tabs and hidden iframes, and throttle timers there too. Idle callbacks (requestIdleCallback) run only when a frame has spare time; Safari supports them only in Technology Preview behind a setting, so feature-detect and fall back to setTimeout.

      Check yourself: A rAF callback calls rAF again. When does the new callback run?

      In the next frame, not the current one.

      In productionStaff-level answers

      Animate transform and opacity where you can: they skip layout and are often handled by the compositor. Read layout values (like offsetHeight) before you write styles in a frame, not after, or the browser must lay out twice. For CSS-only motion, prefer CSS transitions or the Web Animations API; use rAF when the value depends on code.

      Check yourself: Name one property you can animate without causing layout.

      transform (or opacity).

      Finished the five steps? Mark it, and its cards join your reviews.

      Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

      Interview questions this answers

      • Where does requestAnimationFrame run relative to tasks and microtasks?