Back to JavaScript
JavaScript
easy
mid

How would you implement a polyfill for setTimeout?

There's no pure-JS way to create a real timer — setTimeout is a host API. The realistic 'polyfill' is a wrapper that adds features (cancellable handle, arg forwarding) or a custom scheduler built on requestAnimationFrame comparing timestamps. The interview is about understanding the event loop and that timers are host-provided.

4 min read·~10 min to think through

This is partly a trick question: you can't implement setTimeout in pure JavaScript — there's no language primitive for "wait N ms." Timers are a host API (browser / Node), backed by the event loop and the OS. So the answer is one of two things, and you should say which you're doing.

Interpretation 1: a wrapper that adds features

If "polyfill" means "reimplement the API surface with extras":

js
function customSetTimeout(callback, delay = 0, ...args) {
  let cancelled = false;
  // still delegates to the host timer — there's no other source of "wait"
  const id = setTimeout(() => {
    if (!cancelled) callback(...args);
  }, delay);
  return {
    clear() { cancelled = true; clearTimeout(id); },
  };
}

You're wrapping the host API to return a richer handle, forward args, etc. — but the actual waiting is still the host's job.

Interpretation 2: a custom scheduler on rAF

If you genuinely can't use setTimeout and want to simulate it, build a scheduler on requestAnimationFrame (also a host API, but a different one) that compares timestamps:

js
function rafTimeout(callback, delay) {
  const start = performance.now();
  let rafId;
  function check(now) {
    if (now - start >= delay) callback();
    else rafId = requestAnimationFrame(check);
  }
  rafId = requestAnimationFrame(check);
  return () => cancelAnimationFrame(rafId);
}

This approximates it — but it's frame-bound (~16ms granularity), pauses in background tabs, and still relies on a host API. It proves the point: every approach delegates to some host capability.

What the interview is really testing

The event loop. setTimeout(fn, delay) doesn't run fn after exactly delay ms — it queues fn as a macrotask after at least delay ms; it runs once the call stack is clear and microtasks have drained. delay is a minimum, not a guarantee. Knowing that — and that timers are host-provided, not language features — is the actual deliverable.

The framing

"Strictly, you can't — setTimeout is a host API, not a JS language feature; there's no pure-JS way to 'wait.' So I'd clarify the intent: either a wrapper that adds a cancellable handle and arg forwarding while still delegating the wait to the host timer, or a requestAnimationFrame-based scheduler that approximates it by comparing timestamps. Both still lean on a host API. The real content is the event loop: setTimeout queues a macrotask after at least the delay — it's a minimum, not a precise schedule."

Follow-up questions

  • Why is setTimeout's delay a minimum, not a guarantee?
  • Where does setTimeout's callback go in the event loop?
  • What's the difference between a macrotask and a microtask?
  • What does setTimeout(fn, 0) actually do?

Common mistakes

  • Claiming you can implement a real timer in pure JS.
  • Not mentioning that timers are host-provided.
  • Thinking the delay is exact.
  • Not connecting it to the event loop / macrotask queue.

Performance considerations

  • Timer callbacks are macrotasks — they wait behind the current stack and all microtasks. Heavy synchronous work or microtask floods delay them. rAF-based approximations are capped at frame rate and pause when the tab is hidden.

Edge cases

  • setTimeout(fn, 0) still waits for the stack and microtasks to clear.
  • Nested timeouts clamped to a minimum (~4ms) by browsers.
  • Background tabs throttling timers to ~1s.
  • Long synchronous work delaying the callback well past the delay.

Real-world examples

  • Wrapping setTimeout to return a cancellable handle in utility libraries.
  • Explaining to a teammate why a setTimeout(fn, 100) fired 300ms late under load.

Senior engineer discussion

Seniors recognize the trick — timers are host APIs — clarify which 'polyfill' is meant, and pivot to the real substance: the event loop, macrotask vs microtask ordering, and that the delay is a lower bound affected by clamping and tab throttling.

Related questions