Facebook Pixel

How to Build a Self-Correcting Timer in JavaScript

setTimeout drifts over time. A self-correcting timer compensates. Here is how to build one.

How to Build a Self-Correcting Timer in JavaScript

setInterval and recursive setTimeout drift over time because setTimeout does not guarantee exact timing. A self-correcting timer compensates for the drift.

The Problem: Drift

setInterval(() => { console.log("tick", Date.now()); }, 1000);

Over time, the actual interval drifts because:

  • The callback itself takes time to run.
  • The event loop may be busy.
  • Background tab throttling.
  • Timer clamping.

After an hour, the ticks may be seconds off.

The Solution: Self-Correcting Timer

function preciseInterval(fn, interval) { let expected = Date.now() + interval; function step() { const drift = Date.now() - expected; if (drift <= interval) { fn(); } expected += interval; setTimeout(step, Math.max(0, interval - drift)); } setTimeout(step, interval); }

How It Works

  1. expected is when the next tick should fire.
  2. Each tick measures the drift (how late it is).
  3. The next setTimeout delay is adjusted by the drift: interval - drift.
  4. If the drift is too large (more than one interval), it skips the tick to catch up.

Example

preciseInterval(() => { console.log("tick", new Date().toISOString()); }, 1000);

Even if the browser is busy, the timer adjusts to stay close to the target.

When to Use

  • Clocks and timers that need to be accurate.
  • Animation loops that need consistent timing.
  • Polling that should not drift.

When Not to Use

  • Visual animations: use requestAnimationFrame instead (syncs with the refresh rate).
  • Simple repeating tasks where drift does not matter.

The Takeaway

A self-correcting timer compensates for setTimeout drift by measuring the drift and adjusting the next delay. Use it for clocks, timers, and polling that need accuracy. For visual animations, use requestAnimationFrame instead.

A timer that measures the drift (how late each tick is) and adjusts the next setTimeout delay to compensate. This keeps the interval close to the target despite setTimeout's minimum-delay behavior and event loop delays.

Because the callback itself takes time to run, the event loop may be busy, background tab throttling applies, and timer clamping adds minimum delays. These accumulate, causing drift over time.

It tracks when the next tick should fire (expected). Each tick measures the drift (Date.now() - expected). The next setTimeout delay is adjusted by the drift: interval - drift. If the drift is too large, it skips a tick to catch up.

For clocks, timers, and polling that need accuracy over time. When drift would cause visible problems (a clock showing the wrong time, polling missing events). Not for visual animations (use requestAnimationFrame).

requestAnimationFrame. It syncs with the browser's refresh rate (usually 60fps) and runs before the next paint. It is smoother and more efficient than any setTimeout-based timer for visual updates.

Ready to master React completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.