JavaScript Projects
Build a Stopwatch/Timer App
Build a production-grade Stopwatch and Timer application in JavaScript using performance.now(), requestAnimationFrame, state management, and DOM controls.
1. Introduction
Building a Stopwatch / Countdown Timer application is a classic frontend project that tests your mastery of time measurement, state management, event handling, and DOM rendering. Instead of relying on drift-prone setInterval loops, high-precision timing uses performance.now() or requestAnimationFrame for millisecond accuracy.
2. Why It Matters
Simple implementations using setInterval(fn, 1000) suffer from timer drift because single-threaded JavaScript execution delays callbacks when the main thread is busy. Using timestamps with delta calculations guarantees exact time tracking regardless of CPU load or tab throttling.
3. Real-World Analogy
Think of a Track & Field Race Official:
- Flawed Approach (Counting steps): The official tries to count their own heartbeats to measure how long a runner takes to cross the finish line. If the official gets distracted (main thread busy), the count slows down and recorded times become wrong.
- Drift-Free Timestamp Approach (Digital Wall Clock): The official notes the exact wall clock time when the starter pistol fires (
T_{start}), and subtracts it from the current wall clock time (T_{current}). The elapsed durationT_{current} - T_{start}is always mathematically exact.
4. Core Technical Architecture
Our application maintains an explicit state object and calculates elapsed time dynamically:
5. Time Formatting Helper
Convert milliseconds into formatted MM:SS.ms strings using string padding:
6. Practical DOM Integration Example
7. Common Mistakes
- Incrementing state counters directly inside interval loops: Writing
counter += 1inside a 1-second interval loop will drift significantly over time due to event loop queue delays. Always calculate time using timestamps (Date.now()orperformance.now()).
8. Quick Quiz
Q1: Why is performance.now() preferred over Date.now() for timing applications?
A) Date.now() returns strings while performance.now() returns integers
B) performance.now() provides sub-millisecond precision and is monotonic (unaffected by system clock changes)
Answer: B — performance.now() offers high-resolution timestamps and is monotonic, preventing time jumps if the system clock updates.
9. Scenario-Based Challenge
The Countdown Mode Feature:
Extend the Stopwatch class into a CountdownTimer class that accepts a target duration (e.g. 5 minutes), counts down to zero, triggers an onComplete event callback when reaching 00:00.00, and automatically cancels the animation frame loop.
10. Debugging Exercise
Explain why this timer accumulates wrong values after pausing and resuming, and how to fix it:
class BadTimer {
start() {
this.startTime = Date.now();
}
pause() {
// Bug: forgets to save elapsed time before clearing start time!
this.startTime = 0;
}
getElapsed() {
return Date.now() - this.startTime; // returns current timestamp when paused!
}
}
View Solution
Diagnosis: Setting startTime = 0 without storing the elapsed duration causes Date.now() - 0 to evaluate to the current Unix timestamp (billions of milliseconds) when reading elapsed time after pause.
Fix: Accumulate previously elapsed time before clearing the start reference:
class GoodTimer {
constructor() {
this.accumulated = 0;
this.startTime = 0;
}
start() {
this.startTime = performance.now();
}
pause() {
this.accumulated += performance.now() - this.startTime;
this.startTime = 0;
}
getElapsed() {
if (!this.startTime) return this.accumulated;
return this.accumulated + (performance.now() - this.startTime);
}
}
11. Interview Questions
🟢 Q1: Explain how you would prevent timer drift when implementing a long-running timer in JavaScript.
Answer: Timer drift happens when relying on relative increments inside setInterval or setTimeout loops because main thread tasks delay callback executions.
To eliminate drift:
1. Store the initial start timestamp (T_{start}) using performance.now().
2. On every tick (scheduled via requestAnimationFrame or short intervals), query the current timestamp (T_{now}).
3. Calculate actual elapsed time as T_{now} - T_{start} + T_{accumulated}.
This approach calculates exact time deltas independently of loop execution frequency.
12. Production Considerations
- • Background Tab Throttling: Browsers throttle
requestAnimationFrameandsetIntervalloops when tabs are inactive to save battery. To ensure accuracy when users return to your tab, always recalculate elapsed time using stored timestamps upon tab visibility changes (viadocument.onvisibilitychange).