Skip to main content

The requestIdleCallback() function

requestIdleCallback(callback, [options]) registers a callback that runs when the browser is not busy painting or handling events, that is, during idle time. It is a way to do background work without stealing time from rendering and without freezing the interface.

Theory

TL;DR

  • requestIdleCallback(fn) puts fn into the idle callback queue with the lowest priority.
  • The callback receives a deadline object with timeRemaining() and didTimeout.
  • It is called in an idle window between frames, once rendering and microtasks have finished.
  • { timeout: N } guarantees the call happens no later than N ms, even if no idle window appeared.
  • It returns a numeric ID and is cancelled with cancelIdleCallback(id).
  • Not for critical work: on a loaded or hidden tab the call can be postponed for a long time.

Quick example

javascript
requestIdleCallback((deadline) => { while (deadline.timeRemaining() > 0) { console.log('Doing a background task'); } console.log('The browser is busy again, waiting for the next idle window'); });

Here:

  • deadline is a special object passed into the callback;
  • deadline.timeRemaining() shows how many milliseconds are still free before the next frame;
  • you do work while there is time, and if you did not finish, the browser calls the callback again in the next idle window.

What idle time is and how the call works

The browser paints frames roughly 60 times per second, that is every 16.6 ms. If some frame took only, say, 10 ms, the browser is left with about 6 ms of free time, and that is exactly where your requestIdleCallback can land. This is how you run minor or background tasks that are not time critical without slowing the interface down.

Step by step:

  1. You call requestIdleCallback(fn).
  2. The browser adds fn to the idle callbacks task queue.
  3. Once the Event Loop, painting and microtasks are done and there is still time before the next frame, the browser calls your callback.
  4. If the tab is inactive or there is no free time, the callback can be postponed.

The arguments: deadline and timeout

javascript
requestIdleCallback(callback, { timeout: 2000 });
  • callback(deadline) is a function that receives an object:
    • deadline.timeRemaining(), how many milliseconds are left "until busy";
    • deadline.didTimeout, true if the timeout expired and the callback was invoked under duress.
  • options.timeout is the maximum waiting time. If no free moment ever appears, the callback still runs after the given number of milliseconds.

Checking didTimeout lets you tell the two modes apart: during a real idle window you can safely work while timeRemaining() > 0, whereas on a forced call timeRemaining() is usually zero and you should do only the bare minimum.

Splitting heavy work into chunks

javascript
const tasks = Array.from({ length: 10000 }, (_, i) => i); function processTasks(deadline) { while (deadline.timeRemaining() > 0 && tasks.length > 0) { const task = tasks.shift(); // do a portion of the work console.log('Task processed', task); } if (tasks.length > 0) { requestIdleCallback(processTasks); } } requestIdleCallback(processTasks);

This approach lets you process thousands of items without lag: the UI stays responsive because the work happens in the pauses between frames. The key detail is re-registering the callback until the queue is empty, since a single call is almost never enough.

Comparison with setTimeout and requestAnimationFrame

CriterionsetTimeoutrequestAnimationFramerequestIdleCallback
GoalRun after N msRun before the frame is repaintedRun in the pause between frames
PriorityMediumHigh (for animations)Low (background tasks)
Depends on loadNoYes, synchronised with the frameYes, called only when the browser is free
On an inactive tabMay be throttledIs pausedMay be delayed for a long time
Used forTimers, delaysAnimations, smooth updatesLight background computation, caching, prefetch logic

Browser support and a fallback

requestIdleCallback is not available in every browser, Safari in particular does not have it, so it is better to keep a fallback:

javascript
const ric = window.requestIdleCallback || function (cb) { return setTimeout(() => cb({ timeRemaining: () => 0, didTimeout: true }), 1); };

It must not be used for critical tasks, because it may not fire for a long time if the tab is under load.

Summary:

PropertyValue
What it doesCalls the callback when the browser has free time
Passes an objectdeadline with timeRemaining() and didTimeout
When it runsIn idle windows between frames
Used forBackground tasks, caching, prefetching data
Cancelled withcancelIdleCallback(id)
ReturnsA numeric ID, like timers do

Common mistakes

  • Relying on it for critical logic. The callback may not run for minutes if the page is constantly busy. Set a timeout if you need a guarantee.
  • Ignoring timeRemaining(). A loop without a remaining-time check eats the frame and brings back exactly the lag you were escaping.
  • Not re-registering the callback. A single idle window rarely fits all the work, so you have to call requestIdleCallback again at the end if the queue is not empty.
  • Painting from it. DOM changes inside an idle callback happen after the frame and cause an extra reflow. Visual updates belong in requestAnimationFrame.
  • Forgetting about missing support. Without a fallback the code simply does nothing in Safari, and the bug stays unnoticed for a long time.
  • Not cancelling a scheduled callback. After a component disappears the call stays in the queue, so cancelIdleCallback(id) is needed.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.