Skip to Content
Course content

189: Building a Pomodoro Timer

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

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 CONFIG object that holds durations for both work (25 mins) and break (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").
Rating
0 0

There are no comments for now.

to be the first to leave a comment.