ESC

Type to search the knowledge base.

requestIdleCallback

Run low-priority work in idle periods — deadline.timeRemaining, timeout option, polyfill with rAF, and what not to put idle.

advanced3 min read
  • javascript
  • requestidlecallback

The main thread is busy with input, scripts, and rendering. requestIdleCallback schedules work for moments the browser believes it has spare time — analytics, prefetch warmup, non-critical indexing — without competing as hard as a naked setTimeout(0) loop.

const id = requestIdleCallback((deadline) => {
  while (deadline.timeRemaining() > 0 && tasks.length) {
    const task = tasks.shift();
    task();
  }
  if (tasks.length) {
    requestIdleCallback(/* same */ , { timeout: 2000 });
  }
}, { timeout: 2000 });

cancelIdleCallback(id);

deadline

API Meaning
timeRemaining() ms estimate left in this idle period (can be 0)
didTimeout true if invoked because timeout fired, not true idle
function pump(deadline) {
  do {
    workOne();
  } while (tasksLeft() && deadline.timeRemaining() > 1);

  if (tasksLeft()) {
    requestIdleCallback(pump, { timeout: 1000 });
  }
}

Keep each unit of work small. One giant task inside idle still freezes the page.

timeout option

Without timeout, idle work may be delayed a long time on busy pages. With timeout: 2000, the callback runs by then even if the browser never felt “idle” — then didTimeout is true and timeRemaining() may be 0; still yield cooperatively.

Support and polyfill shape

Safari lagged for years; check current Baseline. Fallback:

window.requestIdleCallback =
  window.requestIdleCallback ||
  function (cb) {
    const start = Date.now();
    return setTimeout(() => {
      cb({
        didTimeout: false,
        timeRemaining() {
          return Math.max(0, 50 - (Date.now() - start));
        },
      });
    }, 1);
  };

Not perfect (doesn’t know real frame budget) but good enough for non-critical queues.

What belongs idle vs not

Idle OK Not idle
Beacon analytics Input response
Hydration of below-fold widgets Animations (use rAF)
Building search indexes of local data Anything with hard latency SLAs
Prefetch of likely routes Security-sensitive immediate work

React’s older scheduler ideas and scheduler package overlap this space; framework integrations may already chunk rendering — don’t double-schedule naively.

Interaction with microtasks

Idle callbacks are tasks in the event loop sense (macrotask family), not microtasks. Don’t expect them between promise chains mid-task.

Interview answer (out loud)

“requestIdleCallback runs a callback when the browser has free time, with a deadline so I can chunk work via timeRemaining. The timeout option guarantees a latest run. I use it for low-priority non-visual work, not animations or input. I polyfill carefully where unsupported.”

Chunking algorithm sketch

function runIdle(tasks, { timeout = 1000 } = {}) {
  function next(deadline) {
    while (tasks.length && (deadline.timeRemaining() > 0 || deadline.didTimeout)) {
      const t = tasks.shift();
      t();
      if (deadline.didTimeout) break; // do one and reschedule when forced
    }
    if (tasks.length) requestIdleCallback(next, { timeout });
  }
  requestIdleCallback(next, { timeout });
}

Tune batching so forced-timeout runs don’t monopolize the main thread.

Analytics beacons

Idle is a good time to flush queued analytics, but use visibilitychange / pagehide + navigator.sendBeacon for last-chance delivery when the tab closes — idle may never come.

Further reading

Related guides