CCommons

I built a tiny quiz bot for formula revision, and it is more honest with me than I am

A 45-line flashcard script, five boxes, and why my "I know this" turned out to be wrong 30% of the time.

Tushar PawarDropper Sample

22 Sept 2026 ยท 5 min read

Photo: Dietmar Rabich ยท CC BY-SA 4.0 ยท via Wikimedia Commons

This is my second attempt at JEE and my free time is basically zero. But I allow myself two hours of coding on Sunday evenings, because without it I get a weird kind of restlessness where every problem starts looking the same. This month's Sunday project was a quiz bot for formula revision.

"Bot" is a bit of a grand word. It's a script that runs in the terminal, asks me a question, lets me see the answer, asks me to mark myself right or wrong, and then decides when to show the card again. That's it. But it solved a problem I actually had.

The problem

I revise formulas by reading them. I read the page of "Rotational motion, important results", think "yes, I know all this", turn the page. Then in a test I can't remember the moment of inertia of a hollow sphere. Reading gives you the feeling of knowing. It is not the same as knowing, because in the test nobody puts the formula in front of you and asks "do you recognise it?". They ask you to produce it.

So I needed active recall: question first, answer hidden. And I needed spaced repetition: cards you get right come back later and later, cards you get wrong come back soon. There are great apps that do this. I wanted to build the thing once to see how the scheduling works.

The Leitner idea

It's the easiest scheduling method to code. You have 5 boxes.

  • A new card starts in box 1.
  • You get it right: it moves up one box.
  • You get it wrong: it goes all the way back to box 1.
  • Box 1 is reviewed after 1 day, box 2 after 2 days, box 3 after 4, box 4 after 8, box 5 after 16.

Cards you know well drift to box 5 and bother you rarely. Cards you keep forgetting stay in box 1 and show up every day, like a person who owes you money.

The cards

Questions live in a plain CSV file, so adding a card means adding a line:

question,answer
Dimensional formula of torque,ML^2T^-2
Escape velocity from a planet,sqrt(2GM/R)
Bond order of N2,3
Derivative of tan x,sec^2 x
MI of a solid sphere about a diameter,(2/5)MR^2
"Circle with centre (h, k) and radius r","(x-h)^2 + (y-k)^2 = r^2"

If a question or answer contains a comma, put it in double quotes (last line above). I broke the file once by forgetting this.

The script

import csv
import json
import random
from datetime import date, timedelta
from pathlib import Path

CARDS = Path("cards.csv")
STATE = Path("state.json")
GAP_DAYS = {1: 1, 2: 2, 3: 4, 4: 8, 5: 16}


def load_cards():
    with CARDS.open(newline="", encoding="utf-8") as f:
        return {row["question"]: row["answer"] for row in csv.DictReader(f)}


def load_state():
    if STATE.exists():
        return json.loads(STATE.read_text(encoding="utf-8"))
    return {}


def save_state(state):
    STATE.write_text(json.dumps(state, indent=2), encoding="utf-8")


def is_due(entry, today):
    return date.fromisoformat(entry["due"]) <= today


def main():
    cards = load_cards()
    state = load_state()
    today = date.today()

    due = [q for q in cards if q not in state or is_due(state[q], today)]
    random.shuffle(due)
    print(f"{len(due)} cards due today")

    for q in due:
        input(f"\nQ: {q}\n(press Enter to see the answer) ")
        print("A:", cards[q])
        ok = input("Did you get it? (y/n) ").strip().lower() == "y"

        box = state.get(q, {"box": 1})["box"]
        box = min(box + 1, 5) if ok else 1
        state[q] = {"box": box, "due": str(today + timedelta(days=GAP_DAYS[box]))}
        save_state(state)

    print("Done for today.")


if __name__ == "__main__":
    main()

A few things I learned writing it:

  • A dictionary comprehension {row["question"]: row["answer"] for row in ...} turns the CSV into a lookup table in one line.
  • date.fromisoformat and str(date) convert dates to text and back. I store the due date as text because JSON has no date type.
  • state.get(q, {"box": 1}) gives a default for cards I've never seen, so I don't have to special-case new cards.
  • I call save_state after every card, not at the end. So if I get annoyed and Ctrl+C in the middle, I lose nothing.

The bugs

The infinite wrong card. In my first version a wrong answer put the card in box 1 with due date = today. So the same card came back the moment I finished, forever, until I got it right. Not terrible, but stressful. Now box 1 means "tomorrow".

Self-grading is the weak point. The bot trusts me. And I'm someone who presses y because I "sort of" remembered. So I made a rule outside the code: I must write the answer on paper before pressing Enter. If the paper and the answer disagree, it's an n. The first week I was wrong on a lot of cards I would have sworn I knew. About 30% of my "yes, I know this" feelings were fake, which is a rough count from my notes, not a statistic.

Edited questions become new cards. The state is keyed by the question text, so if I fix a typo, the old progress is lost. I accept that. It's a 45-line script.

A tiny addition

At the end I print how many cards are in each box:

from collections import Counter

counts = Counter(entry["box"] for entry in state.values())
print({box: counts.get(box, 0) for box in range(1, 6)})

Seeing {1: 14, 2: 9, 3: 6, 4: 3, 5: 2} is a better progress bar than any feeling I have about my preparation.

Why bother when apps exist?

Honestly, for pure revision, an app is probably better, and you should use one if you don't enjoy coding. I enjoyed this. It taught me file handling, dates, and a scheduling idea I'll reuse. It cost two Sunday evenings. A bad week of revision costs more than that.

But also: do not turn this into a reason to avoid studying. I keep the cards file at about 150 cards, mostly formulas and a few reaction facts. When I'm tempted to add a feature, I add a card instead. That has worked so far.

WhatsAppPost on X

11 responses

Sign in to join the conversation.

  • [+]Kabir Joshi11thSampleยท 27d ago

    this is exactly the kind of project I like. One thing, blank lines in the CSV, would that crash it? And the next obvious step is hooking it to a Telegram bot so you can do cards on your phone while waiting for the mess queue

    • [+]Tushar PawarDropperSampleยท 27d agoAuthor

      blank lines are fine, DictReader skips completely empty rows. A row with too few columns gives None for the answer though, so I'd check that. And yes, the phone version is the plan, but I'll wait until after the next mock series, otherwise I'll never study ๐Ÿ˜‚

  • [+]Dev Agarwal12thSampleยท 27d ago

    you realise this is basically a product, right? Quiz/flashcard apps with spaced repetition are a crowded market though, so you'd need a hook. Maybe a version that imports cards from a student's mock test mistakes automatically. Just thinking out loud

    • [+]Siddharth RaoDropperSampleยท 27d ago

      Hook should be "someone actually uses it daily". A friend and I both used a flashcard thing last attempt, we both quit after two weeks. The tool is the easy part. What Tushar did differently is the paper rule. Write first, then reveal.

  • [+]Diya Menon11thSampleยท 27d ago

    omg I want to do this for biology! can the answer be an image? like labelled diagrams of the nephron? or does it have to be text

    • [+]Tushar PawarDropperSampleยท 27d agoAuthor

      Not in this version, it just prints text. But you could put a file name in the answer column, like nephron.png, and have the script open the file using Python's webbrowser or os module. Or just keep the answer text as "see page 3 of diagram notebook" and check it on paper. The simplest thing that works is usually the best.

  • [+]Ishita GoyalDropperSampleยท 27d ago

    love the 5-box schedule. In my study sheet I do a version of this by hand: "revise on day 1, 3, 7, 15, 30" for every topic, with ticks. Yours is that, but automatic. I'd just keep cards short. One fact per card. Cards with three facts end up marked "wrong" because of one missing piece

  • [+]Farhan ShaikhDropperSampleยท 27d ago

    free, runs on any old laptop, no subscription. This is my kind of project. I'm a NEET student but I'd use the same for biology facts. How big is the file once you have like 500 cards? Is it slow?

    • [+]Tushar PawarDropperSampleยท 27d agoAuthor

      Not slow at all. 500 cards is a tiny text file, maybe 30 KB. The script reads the whole thing in a blink. The only slow part is you.

  • [+]Aarav Kulkarni11thSampleยท 26d ago

    Can I make a checklist version? Like a "must-know" list with ticks and the date I last saw each? Sorry, I'm obsessed with checklists. Your Counter print at the end is exactly what I want

  • [+]Lakshmi TripathiDropperSampleยท 26d ago

    the "paper before Enter" rule is the best line in the post. I do the same with a blank page and a pencil.

More from Tushar Pawar