requestIdleCallback
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.
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.
Advantages vs Limitations
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.
Production 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)
}Interview Readiness
Q: What is requestIdleCallback and when would you use it?
A: requestIdleCallback schedules a callback to run during browser idle periods — when the main thread has finished processing the current frame and has spare capacity before the next one. You use it for low-priority background work that should not compete with user interactions: analytics, prefetching, localStorage syncing, or processing large datasets in chunks. The callback receives a deadline object with a timeRemaining() method, which you should check in loops to avoid exceeding the frame budget.
Q: What is the difference between requestIdleCallback and requestAnimationFrame?
A: requestAnimationFrame (rAF) fires at the start of every frame (typically 60fps) and is intended for work that must happen in sync with rendering — animations, DOM updates. requestIdleCallback fires only when the browser has spare time after all high-priority work is done — it is for non-urgent background tasks. rAF guarantees a callback every frame; rIC may not fire at all on a busy page unless you set a timeout.