Back to APIs

requestIdleCallback

Concurrency

An API that schedules low-priority work to run during browser idle periods, keeping the main thread available for user interactions and animations while still completing background tasks.

SupportChrome 47+, Firefox 55+, Edge 79+. Safari: Not supported (use polyfill or setTimeout fallback).
BaselineNewly available
ImpactMedium
Popularity🔥 60

When to Use

  • Prefetching or preloading data that the user has not yet requested.
  • Logging, analytics, and telemetry that do not need to be sent immediately.
  • Parsing or processing large datasets in the background without blocking user input.
  • Saving draft state or syncing to localStorage without delaying the current interaction.
  • Running cleanup tasks (e.g. clearing expired cache entries) after the critical path.

When NOT to Use

  • For work that must complete before the next render or user interaction.
  • For animations — use requestAnimationFrame instead.
  • For time-sensitive operations — idle callbacks may be delayed indefinitely on busy pages.
  • As a general replacement for async/await — it is only for genuinely deferrable background work.
  • In Safari without a polyfill — requestIdleCallback is not supported.

Code Examples

Chunked background processing

Processes items incrementally across multiple idle periods. The deadline check ensures we never over-budget a single frame. The timeout ensures processing completes within 2 seconds even on busy pages.

// Process a large array during idle periods
function processInChunks(items, processFn) {
  let index = 0

  function processChunk(deadline) {
    // Keep working while there is idle time and items remaining
    while (deadline.timeRemaining() > 0 && index < items.length) {
      processFn(items[index])
      index++
    }

    // If not done, schedule the next chunk
    if (index < items.length) {
      requestIdleCallback(processChunk, { timeout: 2000 })
    }
  }

  requestIdleCallback(processChunk, { timeout: 2000 })
}

Safari-safe polyfill wrapper

Wraps requestIdleCallback with a Safari-compatible fallback that mimics the deadline API using a 50ms approximation.

// Safe cross-browser wrapper
const scheduleIdleWork = (callback, options) => {
  if (typeof window.requestIdleCallback === 'function') {
    return window.requestIdleCallback(callback, options)
  }
  // Safari fallback: setTimeout approximation
  const start = Date.now()
  return setTimeout(() => {
    callback({
      didTimeout: false,
      timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
    })
  }, 1)
}

Advantages

  • Browser-coordinated: the browser decides the best time to run the callback, respecting frame budget.
  • Provides a `deadline` object telling you how much time remains before the next frame.
  • The `timeout` option ensures the callback runs eventually, even if idle time is scarce.
  • Keeps UI interactions at 60fps while still making background progress.

Limitations

  • Not supported in Safari — requires a polyfill (e.g. `setTimeout(() => fn(), 1)`).
  • Idle time is not guaranteed — on very busy pages, idle callbacks may wait a long time.
  • The deadline is a hint, not a hard limit — the callback can still exceed it if not coded carefully.
  • Should not be used for anything that touches the DOM — DOM changes trigger layout.

Common Mistakes

  • Performing DOM reads or writes inside an idle callback — this triggers layout and defeats the purpose.
  • Not checking deadline.timeRemaining() inside long loops — allows the callback to run over budget.
  • Not providing a timeout option — the callback may never run on a CPU-busy page.
  • Using it in Safari without a fallback — causes a runtime error.

References