TZWZ's personal page
How to do countdown timers in JavaScript
01.02.2026
JavaScript

The naive approach

When developing your application, you may run into a problem where you need to show the user a timer that will count down from some value to zero. This may be, for example, a functionality to show how much time is left until the user can do something, or to show when something will happen, or the time until something becomes available. The easiest way would be to just create an HTML element, attach setInterval running every second to each of the timer elements, and call it a day.

The problem with this approach is the browser's behavior. When a user changes tabs, minimizes a browser window, or makes a browser tab non-focused in any way, the JS runtime may (and will) react to events slower. This means your function, passed to setInterval, will get called less frequently. So your timer will start to drift away from the real time left, and the only way for your user to get real time will be to refresh the browser window or somehow reload the timer element (i.e., making the page unload it and load anew).

Date based solution

To solve this problem, you have to use the Date object and calculate the real time passed between calls of the function you passed to setInterval. First, the HTML for the timer:

<div class="timer" data-time-left="5294493544379"></div>

Time left is done by converting the new Date() object into a number, for example by using Number(val). What we need is basically end time with precision of milliseconds. The reason for milliseconds is that this is what we get by converting Date into a number. So less work to get a number, and we can reduce precision when calculating anyway.

For the JS code, the implementation for the update function looks like that:

let TIMERS_UPDATER = null;

export function start_timers() {
  if (TIMERS_UPDATER === null) {
    return;
  }
  let last_time = Date.now();
  TIMERS_UPDATER = setInterval(() => {
    const now = Date.now();
    const diff = now - last_time;
    last_time = now;
    for (const el of document.getElementsByClassName("timer")) {
      const time = el.dataset.timeLeft - diff;
      el.dataset.timeLeft = time;
      set_timer(el, time);
    }
  }, 1000);
}

function set_timer(element, time) {
  let seconds = time / 1000;
  if (seconds < 0) {
    seconds = 0;
    element.classList.remove("timer");
  }
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor(seconds / 60) % 60;
  seconds = Math.round(seconds % 60);
  const time_string = [hours, minutes, seconds]
    .map((v) => (v < 10 ? "0" + v : v))
    .join(":");
  element.innerHTML = time_string;
}

The start_timers is the only function that needs to be visible on the outside. It starts with setInterval for updating all timers found on the page. To do that, it checks how much real time passed between calls and updates timers based on this.

The set_timer converts total seconds left to a HH:MM:SS string. The HH can take more than two characters if the time left is long enough.

Conclusion

By using Date objects and calculating time difference, we always get the correct time difference between updates. Additionally, we have one setInterval running instead of spawning a new one for each timer. Even if we are off by one second due to rounding, the error never grows further. With this, we also avoid using web workers, which would complicate implementation. This is a pretty simple and low-maintenance solution that you can adjust to your needs easily.

Related projects