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)
189: Building a Pomodoro Timer
I've seen a lot of developers start their first timer project by reaching for setInterval and simply decrementing a variable every 1,000 milliseconds. It feels intuitive. You set a variable to 1500 (for 25 minutes), subtract 1 every second, and update the screen. On the surface, it looks perfect. But here is the problem: setInterval is not a precision instrument.
The Myth of the Perfect Second
The misconception is that setInterval(fn, 1000) guarantees your code runs exactly every 1,000ms. In reality, JavaScript's event loop can be delayed. If a complex function is running or the browser is struggling with a heavy render, your "second" might actually take 1,010ms or 1,100ms. Over the course of a 25-minute Pomodoro session, those tiny slivers of drift add up. You'll find your timer is out of sync with the actual wall clock by several seconds.
// The "Wrong" Way: Drift-prone
let timeLeft = 1500;
setInterval(() => {
timeLeft--; // This assumes exactly 1 second has passed
updateDisplay(timeLeft);
}, 1000);
Calculating Delta Time for Precision
To build a timer that actually holds up, you have to stop relying on the interval to track time and start relying on the system clock. I always tell my juniors: use the interval only as a heartbeat to trigger a UI refresh, but calculate the remaining time by comparing the current timestamp to the target end time.
By capturing Date.now() when the timer starts and subtracting it from the current time during every tick, you get the exact elapsed time, regardless of whether the event loop lagged for a few milliseconds.
const WORK_TIME = 25 * 60 * 1000; // 25 minutes in ms
let endTime;
let timerId;
function startTimer() {
endTime = Date.now() + WORK_TIME;
timerId = setInterval(() => {
const now = Date.now();
const remaining = endTime - now;
if (remaining <= 0) {
clearInterval(timerId);
alert("Time for a break!");
return;
}
updateDisplay(remaining);
}, 100); // Update more frequently (every 100ms) for a snappy UI
}
function updateDisplay(ms) {
const seconds = Math.ceil((ms / 1000) % 60);
const minutes = Math.ceil(ms / (1000 * 60));
document.getElementById('timer').innerText = `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
Notice that I changed the interval to 100ms. Since we are calculating the difference between timestamps, updating the UI more often doesn't hurt our accuracy—it actually makes the timer feel more responsive. If we were just subtracting 1 from a variable, updating every 100ms would make the timer run 10 times too fast.
Managing Pomodoro States
A real Pomodoro timer isn't just one countdown; it's a cycle. You have the "Work" phase and the "Break" phase. I recommend using a simple state object to track where the user is in the cycle. This keeps your logic clean and prevents you from having to write two entirely separate timer functions.
const CONFIG = {
work: 25 * 60 * 1000,
break: 5 * 60 * 1000
};
let currentState = 'work';
function handleCycle() {
currentState = (currentState === 'work') ? 'break' : 'work';
const duration = CONFIG[currentState];
endTime = Date.now() + duration;
// Notification logic here...
}
By decoupling the configuration (the durations) from the execution (the timestamp subtraction), you can easily add a "Long Break" after four cycles without rewriting your core timing engine.
📋 Practical Task
Implementing the Pomodoro Cycle Switcher
You have been provided with a basic timer that counts down from 25 minutes. Your task is to modify the code to implement the "Automatic Cycle Switch."
- Create a
CONFIGobject that holds durations for bothwork(25 mins) andbreak(5 mins). - Implement a state variable to track if the user is currently in
'work'or'break'mode. - Modify the timer's completion logic so that when the work timer hits zero, it automatically switches the state to
'break'and starts the 5-minute countdown immediately. - Ensure the UI updates to show which mode is currently active (e.g., "Work Mode" vs "Break Mode").
There are no comments for now.