Skip to Content
Course content

185: Building a Countdown Timer App

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Alright, let's build something a bit more interactive. You've seen these on launch pages or holiday promos everywhere: the countdown timer. It seems simple on the surface—just subtract one from a number every second—but if we do it the naive way, we're going to run into some annoying synchronization issues.

The naive approach and why it fails

My first instinct is usually to just set a variable and use setInterval to decrement it. Let's see what happens when I try that.


let secondsLeft = 10;
setInterval(() => {
  secondsLeft--;
  console.log(secondsLeft);
}, 1000);

At first glance, this works. But here's the problem: setInterval isn't a precision instrument. If the browser tab is throttled, or if the CPU spikes, that "1000ms" might actually be 1005ms or 1010ms. Over an hour, your timer will actually be several seconds slow. If this is for a high-stakes product launch, that's a bug. We shouldn't be counting down; we should be calculating the difference between now and a target date.

Calculating the delta

Instead of relying on the interval to keep time, I'm going to set a fixed target date in the future and calculate the remaining time on every tick. This way, even if the interval lags, the next tick will simply calculate the correct remaining time based on the actual system clock.


const targetDate = new Date('December 31, 2024 23:59:59').getTime();

setInterval(() => {
  const now = new Date().getTime();
  const distance = targetDate - now;
  console.log(distance); 
}, 1000);

Now we have a number, but it's in milliseconds. Looking at a giant number like 154200000 doesn't help the user. I need to slice this number into days, hours, minutes, and seconds. This is where the modulo operator (%) becomes my best friend.

Slicing the time into something human

I'll take that distance and start breaking it down. I know there are 1000ms in a second, 60 seconds in a minute, and so on. I'll use Math.floor because I don't care about fractional seconds in a display timer.


const days = Math.floor(distance / (1000 * 60 * 60 * 24));
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);

console.log(`${days}d ${hours}h ${minutes}m ${seconds}s`);

I've found that using the modulo operator here effectively "strips away" the larger units. For example, distance % (1000 * 60 * 60 * 24) removes all the full days, leaving only the remaining milliseconds that haven't yet formed a full day. Then I divide those remaining milliseconds by the length of an hour to get the hour count.

Stopping the bleed

One thing I almost forgot: what happens when the timer hits zero? If I leave the code as is, distance will become negative, and the timer will start counting backward into the void. I need a way to kill the process.

I'll assign the interval to a variable so I can call clearInterval() the moment the distance drops to zero or below.


const timerId = setInterval(() => {
  const now = new Date().getTime();
  const distance = targetDate - now;

  if (distance < 0) {
    clearInterval(timerId);
    console.log("Time's up!");
    return;
  }
  
  // ... (math and display logic from above)
}, 1000);

By wrapping the logic this way, the app remains performant and accurate. We aren't trusting the timer to be a clock; we're using it as a trigger to check the actual clock.




📋 Practical Task

Build a "Flash Sale" Countdown with Alert

Create a countdown timer that targets a date exactly 24 hours from the moment the page is loaded. The timer should display the remaining hours, minutes, and seconds in a <div> on the page. Once the timer reaches zero, use clearInterval to stop the timer and trigger an alert('The Flash Sale has ended!') to notify the user.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.