Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A countdown built by subtracting one second every time setInterval(..., 1000) fires will drift because it counts callbacks, not elapsed time. Browser timers request that work be scheduled; they do not guarantee exact spacing. Keep a start time or deadline as the source of truth, then calculate the remaining time afresh whenever the display updates.

Why does setInterval make a countdown drift?

setInterval(callback, 1000) asks the browser to schedule a callback with a 1,000-millisecond delay. It does not promise that callbacks will run exactly one second apart. MDN Web Docs puts it plainly: “Note also that the actual amount of time that elapses between calls to the callback may be longer than the given delay.” (MDN Web Docs: Window: setInterval() method)

JavaScript callbacks run through the event loop and cannot interrupt other JavaScript already running on the main thread. If the page is busy, a timer callback must wait. Browsers can also throttle timers in inactive tabs, with behavior that varies by browser. If each late callback still subtracts exactly one second, every delay becomes lost time in the displayed countdown. (MDN Web Docs: Window: setTimeout() method)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Requesting callbacks more frequently does not solve the underlying problem. A shorter delay remains a scheduling request and can still be affected by main-thread work and background-tab policies.

How to make a countdown reflect elapsed time

Record a time reference once, then derive the remaining duration from that reference on each render. The timer callback controls how often the display is refreshed; it should not define how much time has elapsed.

Duration countdown within the current page

For a duration that only needs to be measured within the current page context, use performance.now(). It is a monotonic clock measured relative to the page’s performance time origin, rather than Unix epoch time, and is not affected by system clock adjustments. MDN notes that whether it advances during system sleep has varied across operating systems, so decide how a timer should behave after sleep and resume. (MDN Web Docs: Performance: now() method; MDN Web Docs: High precision timing)

const durationMs = 60_000;
const startedAt = performance.now();

function render() {
  const elapsed = performance.now() - startedAt;
  const remainingMs = Math.max(0, durationMs - elapsed);
  showRemaining(remainingMs);

  if (remainingMs > 0) {
    setTimeout(render, 100); // Refresh cadence only; elapsed time is recalculated.
  }
}

render();

Here, the 100-millisecond timeout is only a display-refresh cadence. If a callback runs late, the next render still derives the remaining duration from the original start time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Deadline that must survive a reload

If a deadline must be stored, restored after reload, or compared with another epoch-based timestamp, use an epoch time such as Date.now(). Because it follows the system wall clock, clock changes can affect the result; account for that if it matters to the application. (MDN Web Docs: High precision timing)

const deadline = Date.now() + durationMs;

function render() {
  const remainingMs = Math.max(0, deadline - Date.now());
  showRemaining(remainingMs);

  if (remainingMs > 0) {
    setTimeout(render, 100);
  }
}

render();

Choose one clock domain deliberately: performance.now() is relative to a time origin, while Date.now() is epoch-based. Do not subtract one from the other without an explicit conversion.

What should happen when a tab returns to the foreground?

When the page becomes visible again, recompute the display from the stored start time or deadline and repaint it immediately. Do not replay one callback for every missed second: those callbacks are not elapsed-time records. The Page Visibility API can tell an application when the document’s visibility state changes, while the precise background throttling policy remains browser-dependent. (MDN Web Docs: Page Visibility API)

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Which timer mechanism should you use?

Need Suitable mechanism What it does not guarantee
Simple countdown display setInterval or recursive setTimeout Exact callback timing. Derive the remaining time from a clock or deadline. (MDN Web Docs; MDN Web Docs)
Work that must not overlap itself Recursive setTimeout Fixed-rate execution. The next call is scheduled after the previous work completes. (MDN Web Docs)
Smooth visual animation requestAnimationFrame Background execution. It is one-shot, generally follows display refresh, and is paused in most browsers when a page is in the background. It schedules presentation; it does not make a countdown deadline accurate by itself. (MDN Web Docs: Window: requestAnimationFrame() method)
Work that can run in a worker Worker timers A universal exact-time guarantee. Moving work off the window’s main-thread tasks does not remove timer constraints or browser lifecycle policies, and the visible UI still needs updating. (MDN Web Docs: WorkerGlobalScope: setInterval() method)

Use recursive setTimeout when it matters that a new work cycle begins only after the prior cycle finishes. It can avoid overlapping work, but it is not a precision clock.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common countdown implementation mistakes

  • Subtracting a fixed amount per callback: Late or throttled callbacks mean callback count does not equal elapsed time.
  • Reducing the interval to improve accuracy: A smaller requested delay does not override event-loop delays or background throttling.
  • Treating recursive setTimeout as an exact clock: It schedules the next call after the current work completes; it does not guarantee a fixed interval.
  • Using requestAnimationFrame for background ticking: Most browsers pause it for hidden pages.
  • Mixing clock domains: performance.now() is relative to a time origin; Date.now() uses epoch time and can reflect system clock changes.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.