thecodex.expert · The Codex Family of Knowledge
Tier 2 · Intermediate · Python Project

Pomodoro Timer

A focus timer that cycles work and break sessions, tracking completed rounds. Build a small state machine around time.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a Pomodoro timer: 25 minutes of work, then a 5-minute break, repeating — counting completed rounds and giving a longer break every fourth one. It teaches building a small state machine: the program moves between “work” and “break” states on a schedule.

Where this shows up: any app with modes or phases — a traffic light, a workout interval timer, a game with rounds, a wizard, a checkout flow. Programs that move between defined states on rules are everywhere.

2 How to Think About It

Think about the cycle of states, before any code:

The plan — in plain English
1. Work for 25 minutes. → 2. Count the completed round. → 3. Break: a short 5-minute break, but every 4th round a longer 15-minute one. → 4. Repeat. The program is always in one state (work or break) and switches between them by rule.

Yes

No

Start: Work state

Work for 25 min

Round complete + 1

Every 4th round?

Long break 15 min

Short break 5 min

3 The Build — explained part by part

Here is the complete timer. To keep it testable and quick, the timing is one function and the schedule logic is another. Each part is explained below.

Pythonpomodoro.py
import time

WORK = 25
SHORT_BREAK = 5
LONG_BREAK = 15

def break_length(round_number):
    # Every 4th round earns a long break.
    if round_number % 4 == 0:
        return LONG_BREAK
    return SHORT_BREAK

def wait_minutes(minutes):
    # Real waiting. Tests do not call this.
    time.sleep(minutes * 60)

def run(rounds=4):
    for round_number in range(1, rounds + 1):
        print(f"Round {round_number}: Work for {WORK} minutes.")
        wait_minutes(WORK)
        rest = break_length(round_number)
        print(f"Break for {rest} minutes.")
        wait_minutes(rest)
    print(f"Done! Completed {rounds} rounds.")

if __name__ == "__main__":
    run()
What each part does — in plain words
WORK, SHORT_BREAK, LONG_BREAK — the durations as named constants at the top, so they are easy to find and change.

break_length(round_number) — the schedule rule: round_number % 4 == 0 is true on rounds 4, 8, 12… (the % gives the remainder, which is 0 every 4th time), earning a long break. This is the state logic, isolated so we can test it.

wait_minutes(minutes) — the actual waiting, kept separate so tests never have to wait.

run(rounds=4) — the main loop: for each round, work, then take the scheduled break. It moves between the work and break states each round.
Common mistakes — and how to avoid them
✗ Using round_number % 4 == 1 — that fires on rounds 1, 5, 9, not the 4th.
✓ Use % 4 == 0 so it fires on rounds 4, 8, 12.
✗ Mixing the waiting into the schedule logic — then you cannot test without waiting.
✓ Keep break_length pure and wait_minutes separate.
✗ Hard-coding 25 and 5 everywhere — hard to adjust.
✓ Use named constants at the top.

4 Test & Prove Each Part

We test the schedule rule — which break each round gets — without any real waiting.

Round 4 earns a long break
Rounds 1-3 get short breaks
Round 8 also earns a long break
Pythontest_pomodoro.py
SHORT_BREAK = 5
LONG_BREAK = 15


def break_length(round_number):
    if round_number % 4 == 0:
        return LONG_BREAK
    return SHORT_BREAK


def test_fourth_round_long():
    """Round 4 gets the long break."""
    assert break_length(4) == LONG_BREAK


def test_early_rounds_short():
    """Rounds 1, 2, 3 get short breaks."""
    assert break_length(1) == SHORT_BREAK
    assert break_length(2) == SHORT_BREAK
    assert break_length(3) == SHORT_BREAK


def test_eighth_round_long():
    """Round 8 also gets a long break."""
    assert break_length(8) == LONG_BREAK

Run with pytest -v. By separating the schedule rule from the waiting, we test the logic instantly — no test sits through a 25-minute timer. Separating decisions from slow actions keeps code testable.

5 The Interface

INPUTroundshow many work sessions
What it expects
run(rounds=4)
OUTPUTsession flowwork/break cycle
What it returns
Round 1: Work for 25 minutes.
Break for 5 minutes.
...
Round 4: Work for 25 minutes.
Break for 15 minutes.
Done! Completed 4 rounds.

6 Run It & Automate It

Run it locally
python3 pomodoro.py
Runs real 25/5-minute cycles. Tip: shrink the constants while testing so you do not wait 25 minutes!

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
Round 1: Work for 25 minutes.
Break for 5 minutes.
Round 2: Work for 25 minutes.
Break for 5 minutes.
...
Round 4: Work for 25 minutes.
Break for 15 minutes.
Done! Completed 4 rounds.
If it breaks — how to fix it
🚨 Long breaks happen on the wrong rounds.
Check you use % 4 == 0, not == 1.
🚨 Tests are slow.
Make sure tests call break_length, never wait_minutes or run.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Custom durations. Let the user set work and break lengths. (Teaches: input.)
  2. Track focus time. Total the minutes worked across a day. (Teaches: accumulating.)
  3. Desktop notification. Alert when a session ends. (Teaches: OS integration.)
What you learned
You learned to build a small state machine — moving between work and break states on a schedule — using the modulo operator for “every Nth” rules, and separating timing from logic so it stays testable. State machines are everywhere in real software. Related: time, Control Flow.