JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
185: Building a Countdown Timer App
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.
There are no comments for now.