Back to JavaScript
JavaScript
easy
mid

How would you implement a polyfill for setInterval?

Implement setInterval using recursive setTimeout: schedule a timeout that runs the callback then reschedules itself. This is actually BETTER than native setInterval — it guarantees a full gap between executions and never stacks callbacks. Return a handle with a clear method.

4 min read·~12 min to think through

Polyfilling setInterval with recursive setTimeout is a classic — and the result is arguably better than the native version.

The implementation

js
function customSetInterval(callback, delay, ...args) {
  let timerId;
  let cancelled = false;

  function tick() {
    if (cancelled) return;
    callback(...args);
    if (!cancelled) timerId = setTimeout(tick, delay); // reschedule AFTER running
  }

  timerId = setTimeout(tick, delay);

  // return a handle to clear it
  return {
    clear() {
      cancelled = true;
      clearTimeout(timerId);
    },
  };
}

// usage
const interval = customSetInterval(() => console.log("tick"), 1000);
// interval.clear();

The idea: setTimeout runs the callback, and only then schedules the next timeout. It recurses forever until cancelled.

Why this is better than native setInterval

Native setInterval schedules executions on a fixed cadence regardless of how long the callback takes. If the callback takes longer than the interval (or the tab is throttled/busy), executions queue up and fire back-to-back with no gap — "callback stacking."

Recursive setTimeout schedules the next run after the current one finishes, so there's always a full delay gap between executions. No stacking, no overlap. This matters a lot for things like polling an API — you never want a second request firing before the first returns.

Things to get right

  • Cancellation — return a handle (or id) with a way to stop it; set a cancelled flag and clearTimeout, so a pending timeout doesn't fire after clear().
  • Forward arguments...args passed through to the callback, like the real API.
  • Re-check cancelled after the callback runs — the callback itself might have cleared the interval.

The framing

"Recursive setTimeout: a tick function that runs the callback, then schedules itself again with setTimeout(tick, delay), repeating until cancelled. I return a handle with a clear() that sets a cancelled flag and clears the pending timeout. The interesting part is this is better than native setInterval — native fires on a fixed cadence and stacks callbacks if one runs long, while recursive setTimeout guarantees a full gap between executions, which is exactly what you want for polling."

Follow-up questions

  • Why does recursive setTimeout avoid callback stacking?
  • How would you handle the callback throwing an error?
  • What happens to timers in a backgrounded browser tab?
  • How would you implement a self-correcting interval that accounts for drift?

Common mistakes

  • Rescheduling before running the callback instead of after.
  • No cancellation mechanism, or only clearing the timeout without a cancelled flag.
  • Not forwarding extra arguments to the callback.
  • Claiming it's identical to setInterval — it's actually more stable.

Performance considerations

  • Recursive setTimeout prevents callback pile-up under load or in throttled tabs — far safer for polling than native setInterval. Neither is precise; drift accumulates, so for timing-critical work compute elapsed time rather than counting ticks.

Edge cases

  • Callback runs longer than the delay.
  • Callback throws — should it stop or continue?
  • clear() called from inside the callback.
  • Browser tab throttling timers when backgrounded.

Real-world examples

  • Polling an API every N seconds without overlapping requests.
  • Animation/game loops and countdown timers that must not stack.

Senior engineer discussion

Seniors implement recursive setTimeout cleanly with proper cancellation, and crucially explain *why* it's superior to native setInterval — guaranteed gap, no stacking — and mention timer drift and tab throttling.

Related questions