Pomodoro Timer TUI in Python

This is a complete, beginner-friendly Pomodoro timer in plain Python.

Read the mental model first

Download source file

Quick Run (from repo root)

1cd Site/static/code/examples/python
2python pomodoro_timer.py

It includes:

  • configurable focus and break durations
  • start/pause/resume/reset
  • completed session count
  • optional terminal bell on transitions

Code (Pythonic + Short)

 1from dataclasses import dataclass
 2from enum import Enum
 3import time
 4
 5
 6class Phase(Enum):
 7    FOCUS = "FOCUS"
 8    BREAK = "BREAK"
 9
10
11@dataclass
12class Pomodoro:
13    focus_seconds: int
14    break_seconds: int
15    phase: Phase = Phase.FOCUS
16    running: bool = False
17    sessions: int = 0
18
19    def __post_init__(self):
20        self.remaining = self.focus_seconds
21
22    def reset(self):
23        self.remaining = self.focus_seconds if self.phase is Phase.FOCUS else self.break_seconds
24        self.running = False
25
26    def tick(self):
27        if not self.running:
28            return
29        self.remaining -= 1
30        if self.remaining > 0:
31            return
32        if self.phase is Phase.FOCUS:
33            self.sessions += 1
34            self.phase = Phase.BREAK
35            self.remaining = self.break_seconds
36            print("\aFocus complete. Break time.")
37        else:
38            self.phase = Phase.FOCUS
39            self.remaining = self.focus_seconds
40            print("\aBreak complete. Focus time.")
41
42    def status(self):
43        m, s = divmod(max(0, self.remaining), 60)
44        state = "RUNNING" if self.running else "PAUSED"
45        return (
46            f"{self.phase.value} {m:02d}:{s:02d} "
47            f"| sessions={self.sessions} | {state}"
48        )
49
50
51def read_minutes(prompt: str, default: int) -> int:
52    raw = input(prompt).strip()
53    if not raw:
54        return default
55    return int(raw) if raw.isdigit() and int(raw) > 0 else default
56
57
58def main():
59    focus_minutes = read_minutes("Focus minutes (default 25): ", 25)
60    break_minutes = read_minutes("Break minutes (default 5): ", 5)
61    timer = Pomodoro(focus_minutes * 60, break_minutes * 60)
62
63    print("Commands: start, pause, resume, reset, status, quit")
64
65    while True:
66        if timer.running:
67            time.sleep(1)
68            timer.tick()
69            print(timer.status())
70            continue
71
72        command = input("> ").strip().lower()
73        if command in {"start", "resume"}:
74            timer.running = True
75            print("Timer running.")
76        elif command == "pause":
77            timer.running = False
78            print("Timer paused.")
79        elif command == "reset":
80            timer.reset()
81            print("Current phase reset.")
82        elif command == "status":
83            print(timer.status())
84        elif command == "quit":
85            print("Good work today.")
86            break
87        else:
88            print("Use: start, pause, resume, reset, status, quit")
89
90
91if __name__ == "__main__":
92    main()

Run It

1python pomodoro_timer.py

Why this is explainable

  • dataclass keeps state compact and readable.
  • Enum makes phase transitions obvious.
  • The app loop has two modes: running ticks or waiting for commands.