Coding diary: I built a Pomodoro tracker in Python (and it taught me more than the Pomodoro did)
Sixty lines, one bug that only appears at midnight, and a very honest graph of how little chemistry I studied.
Kabir Joshi11th Sample
17 Sept 2026 ยท 5 min read

Photo: Mister rf ยท CC BY-SA 4.0 ยท via Wikimedia Commons
I've been teaching myself Python since the summer holidays, mostly from free videos and from breaking things. Last month I decided to stop doing tutorial exercises and build one real thing that I would actually use. The thing was a Pomodoro tracker, because my study sessions kept turning into "five minutes" on my phone that became forty.
If you don't know it: Pomodoro is 25 minutes of focus, 5 minutes break, repeat. My tracker does that in the terminal, and it also writes down how many minutes I studied each subject today. I wanted the log more than the timer, honestly. I wanted proof.
Version 1: the dumb one
Here is the first thing that worked. It's not pretty.
import time
import json
from datetime import date
from pathlib import Path
LOG = Path("sessions.json")
WORK_MIN = 25
BREAK_MIN = 5
def load_log():
if LOG.exists():
return json.loads(LOG.read_text())
return {}
def save_log(data):
LOG.write_text(json.dumps(data, indent=2))
def countdown(minutes, label):
total = minutes * 60
for remaining in range(total, 0, -1):
mins, secs = divmod(remaining, 60)
print(f"\r{label}: {mins:02d}:{secs:02d}", end="", flush=True)
time.sleep(1)
print(f"\r{label}: done! ")
def main():
subject = input("What are you studying? ").strip() or "general"
log = load_log()
today = str(date.today())
log.setdefault(today, {})
try:
while True:
countdown(WORK_MIN, "FOCUS")
log[today][subject] = log[today].get(subject, 0) + WORK_MIN
save_log(log)
countdown(BREAK_MIN, "BREAK")
except KeyboardInterrupt:
print("\nStopped. Today so far:")
for sub, mins in log[today].items():
print(f" {sub}: {mins} min")
if __name__ == "__main__":
main()
Things I learned just from this file:
divmod(remaining, 60)gives you minutes and seconds in one go. I used to do//and%separately like a caveman.\rmoves the cursor back to the start of the line, so the timer updates in place instead of printing 1500 lines. This felt like magic the first time.Ctrl+CraisesKeyboardInterrupt. Catching it means the program ends politely and prints the summary.- JSON is just a text format for dictionaries. I can open
sessions.jsonin Notepad and read it. That is also how I found the bug.
The bugs
Bug 1: it counts sessions I didn't finish. If I press Ctrl+C at minute 3 of a focus block, nothing is logged, which is fair. But if I quit at minute 24, it also logs nothing. I wanted partial credit, otherwise I was tempted to quit and restart to "reset" the timer. Fix: track elapsed time and log it on interrupt.
Bug 2: time.sleep(1) is not exactly one second. Each loop does a print and a sleep, and the print takes a little time too. After 25 minutes my timer was about 6 seconds slow. Not a disaster, but it annoyed me. The fix is to stop counting loops and instead compare against a deadline using the monotonic clock.
Here is the countdown function after both fixes:
def countdown(minutes, label):
"""Returns whole minutes actually completed."""
start = time.monotonic()
end = start + minutes * 60
try:
while True:
remaining = int(end - time.monotonic())
if remaining <= 0:
break
mins, secs = divmod(remaining, 60)
print(f"\r{label}: {mins:02d}:{secs:02d}", end="", flush=True)
time.sleep(0.2)
except KeyboardInterrupt:
done = int((time.monotonic() - start) // 60)
print(f"\r{label}: stopped after {done} min ")
raise
print(f"\r{label}: done! ")
return minutes
Now main() catches the KeyboardInterrupt that gets re-raised, and I add the partial minutes there. I'm skipping that code here because the post is already long, but it's about six more lines.
time.monotonic() is a clock that only goes forward and doesn't care if the computer's date changes. For timers it's the right one. time.time() can jump around if the system clock updates.
The part I actually like: the weekly graph
After two weeks I had a decent sessions.json, so I wrote a tiny report:
def weekly_report(log):
totals = {}
for day, subjects in log.items():
for sub, mins in subjects.items():
totals[sub] = totals.get(sub, 0) + mins
for sub, mins in sorted(totals.items(), key=lambda kv: -kv[1]):
bar = "#" * (mins // 25)
print(f"{sub:<10} {bar} {mins} min")
Each # is one 25-minute block. My output last Sunday looked like this:
maths ################ 400 min
physics ########## 250 min
coding ######## 200 min
chemistry ## 50 min
Look at that. I am a person who says "chemistry is my weak point" and then gives it 50 minutes in a week. I had to see it in # symbols to believe it. No lecture from anyone ever worked as well as that bar.
Also note the "coding" line. The tracker was logging me coding the tracker. Very meta. Very wasteful if you think about it too hard. I decided to count it anyway because I was learning something, but I now keep coding to one 25-minute block on weekdays, otherwise it eats everything.
What I'd do differently
- Use
argparseso I can runpython pomo.py mathsinstead of typing the subject every time. - Play a sound at the end of each block. On most terminals
print("\a")makes a beep, but mine is muted by default, so I need to look at the library options. - Store each session as a list of entries with a start time instead of a single running total per subject. Then I could actually draw a timeline and see that I always crash at 4 pm.
For other class 11 people who want to start
You don't need a course. You need a problem that annoys you. Mine was "I don't know where my time goes." Yours might be "I keep forgetting which mock test mistakes I made" or "I want a random question picker". Write the ugliest possible version in an afternoon. It will have bugs. The bugs are where you learn.
And yes, I know the irony of spending three evenings building a tool to help me study. I've made peace with it. The graph says coding is 200 minutes. I'm choosing to call it "STEM-adjacent".
12 responses
Sign in to join the conversation.
[+][โ]Dev Agarwal12thSampleยท 1mo ago
nice. one actual bug though,
today = str(date.today())is computed once at the start. if you leave it running past midnight, the late-night session still gets logged under yesterday's date. maybe recompute inside the loop. Also if you ever use this for a business thing (like tracking hours), that's the kind of bug that shows up in the invoice[+][โ]Kabir Joshi11thSampleยท 1mo agoAuthor
oh that's a good catch, yes. I only ever run it before 11pm so I never saw it. moving
today = str(date.today())inside the while loop right now, thanks
[+][โ]Tushar PawarDropperSampleยท 1mo ago
Respect for monotonic(). Most people find out about it only after a timer drifts. A small suggestion, read the JSON inside a try/except too. If the program dies mid-write, the file can end up half-written and then
json.loadscrashes on the next start. Also, you could write to a temp file and rename it, but that's maybe overkill for a study timer ๐[+][โ]Pooja Iyer12thSampleยท 1mo ago
omg I'm PCB and I understood like 40% of this and I still loved it. what is JSON exactly? you said "text format for dictionaries" but a dictionary in my head is the thing with words and meanings
[+][โ]Kabir Joshi11thSampleยท 1mo agoAuthor
haha same word, different thing. in Python a dictionary is key -> value pairs, like {"maths": 400, "physics": 250}. so "maths" is the key and 400 is what's stored under it, like a word and its meaning. JSON is that same structure written as plain text so you can save it in a file and read it back later
[+][โ]Mehak AroraDropperSampleยท 1mo ago
I tried a pomodoro app last year and it made me more anxious, every time the timer was about to end I'd panic that I hadn't "achieved" enough in the block. Do you ever feel like that? Not trying to criticise, I like that yours is just a log and not a countdown scoreboard
[+][โ]Kabir Joshi11thSampleยท 1mo agoAuthor
yeah I get this. that's why I care more about the log than the countdown. if a block goes badly I still log it, it just becomes 25 minutes of "studying badly" which is still data. but if the timer stresses you maybe turn it off and only log time, that works too
[+][โ]Aman YadavDropperSampleยท 1mo ago
Good project. One warning. Don't let the tool become the work. You said coding ate 200 minutes. In my room there was a guy with a perfect study tracker and an average rank. Build it once, then use it.
[+][โ]Rohan Sharma11thSampleยท 1mo ago
bro called me out ๐ I spent 2 hours making a notion template last week
[+][โ]Ishita GoyalDropperSampleยท 1mo ago
the bar chart idea is so good, I'm stealing it for my weekly plan. in my sheet I do the same thing manually with a formula and it takes me ten minutes every Sunday. does your script work if two subjects have the same name in different cases, like "Maths" and "maths"? I'd add
.lower()when you take the input[+][โ]Kabir Joshi11thSampleยท 1mo agoAuthor
no it doesn't, they'd be two separate keys ๐
.strip().lower()on the input. adding it to the list of fixes
[+][โ]Harsh Gupta12thSampleยท 1mo ago
the real bug is that chemistry only got 50 minutes and the program still said "done!" like everything is fine





