Why Pomodoro Timers Drift in Background Tabs (and How to Fix It)
August 22, 2026 · DevTools
Why Pomodoro Timers Drift in Background Tabs (and How to Fix It)
You start a 25-minute focus block, switch to your editor, come back at the end — and the timer says 14 minutes left. The culprit isn't the timer app; it's browser tab throttling, and the fix is architectural.
The bug: counting ticks
Naive timers accumulate setInterval callbacks: every 1000 ms, decrement the remaining time. But browsers throttle timers in background tabs — first to once per second, then to once per minute, harder under memory pressure. Each throttled tick is a second your timer silently doesn't count. Ten minutes of deep work in another tab, ten minutes of drift.
The fix: timestamps
The Pomodoro Focus Timer never counts ticks. It records when the phase started (startedAt) and computes remaining time as duration − (now − startedAt) on every UI sample. Date.now() doesn't throttle; the countdown is wall-clock correct no matter how aggressively the tab sleeps. The countdown also mirrors into the tab title, so the remaining focus time is visible from any tab without switching back.
The cycle is a state machine
Focus → short break, four times, then a long break, then repeat. Phase transitions are pure functions — nextPhase(state) — so the sequencing logic is exactly testable, and the current phase (with its session counter toward the long break) is always derivable rather than stored in three conflicting booleans. Durations are configurable: the defaults are the classic 25/5/15, and 52/17 or single 90-minute deep-work blocks work unchanged.
Finishing touches that matter
Phases end with distinct chimes (Web Audio: triple beep for focus, double for breaks — no audio file needed), and notifications are opt-in: permission is requested only when you click "enable", never on page load.
Pair it with steady masking noise from the Ambient Noise Generator, and measure your deep-work baseline with the Typing Speed Test.