Implementing Spaced Repetition from Scratch in Dart
Why I built a custom SRS engine instead of using SM-2, and how offline-first design shaped every algorithmic decision.

Spaced repetition is the backbone of Flashcards Alarm's learning effectiveness. The algorithm determines which cards to show and when, based on how well the user remembers each card. Get it right, and users learn faster with less effort. Get it wrong, and they're either bored reviewing easy cards or frustrated by cards they've never seen.
The
cognitive science is well-established. Hermann Ebbinghaus demonstrated the forgetting curve in 1885 — memory retention decays exponentially without reinforcement. Spaced repetition exploits this by scheduling reviews at the precise moment a memory is about to fade. The mathematical challenge is predicting that moment for each individual card and each individual learner, which is where algorithmic implementations diverge.
The classic SM-2 algorithm (used by Anki) is well-proven but designed for desktop software with reliable internet. Flashcards Alarm needed an SRS engine that works offline, syncs gracefully, and handles the unique constraint that study sessions happen during alarm dismissal — users can't choose which cards to study or when.
The specific limitations of SM-2 that drove the custom implementation were threefold. First, SM-2 assumes the user consciously rates their recall quality on a 0-5 scale, but alarm-context users are groggy and impatient — we needed to infer quality from response time and accuracy instead. Second, SM-2 doesn't account for session constraints; it assumes unlimited review time. Third, SM-2's interval calculations can produce awkward schedules when syncing across time zones, which matters for users who travel.
The custom algorithm tracks four values per card: ease factor (how easy the card is for this user), interval (days until next review), repetition count, and last review timestamp. After each review, the algorithm adjusts these values based on the user's response quality — correct on first try, correct with hesitation, or incorrect.
The ease factor calculation uses a modified Leitner approach. Cards start with an ease factor of 2.5. A correct first-try answer multiplies the interval by the ease factor and increases it by 0.1, up to a maximum of 3.0. A hesitant correct answer — response time exceeding the card's rolling average by 50% — keeps the ease factor unchanged. An incorrect answer resets the interval to one day and decreases the ease factor by 0.2, with a floor of 1.3. This creates a natural difficulty curve where easy cards space out quickly and hard cards stay in frequent rotation.
Offline-first was the hardest constraint. The algorithm runs entirely on the device, storing card state in a local SQLite database. When connectivity returns, Firestore sync merges the local state with cloud state. Conflict resolution uses last-write-wins on a per-card basis, which works because a single user is unlikely to review the same card on two devices simultaneously.
Edge cases in the sync layer consumed more engineering time than the core algorithm. Consider a user who reviews cards on their phone during their morning alarm, then reviews the same deck on a tablet in the evening. The phone's state shows card A at interval 4, but the tablet — which was offline all day — still has card A at interval 2. Simple last-write-wins would overwrite the more recent progress. Our solution timestamps each individual card review event rather than the card state, replays events in chronological order during sync, and recomputes the final state deterministically.
The alarm context added a unique challenge: session length is unpredictable. Some users dismiss the alarm in 30 seconds, others take 5 minutes. The algorithm front-loads the most important cards — those most overdue for review — so even a short session covers the highest-value material.
The card selection UI during alarm dismissal required careful thought about cognitive load. At 7 AM, users can't handle complex interactions. We show one card at a time, full-screen, with large tap targets for 'I knew it' and 'I didn't know it.' The answer reveal uses a simple flip animation rather than a slide or fade — we tested all three, and the flip had the fastest average interaction time. Every hundred milliseconds matters when the goal is forming a study habit, not frustrating a sleepy user.
After six months of production data, our custom algorithm showed 23% better retention rates than a standard SM-2 implementation tested during beta. The improvement came primarily from the alarm-context optimizations — front-loading overdue cards and adapting to variable session lengths.
Looking ahead, we're exploring two algorithmic improvements. The first is cross-card difficulty modeling — if a user struggles with a specific vocabulary pattern (say, Korean verbs ending in -하다), the algorithm should preemptively reduce intervals for similar unseen cards. The second is time-of-day adaptation. Our data shows that recall accuracy varies significantly by review hour, with a 15% drop in accuracy for 6 AM reviews compared to 8 AM. Adjusting the algorithm's difficulty expectations based on alarm time could further improve retention rates.
Spaced repetition is the backbone of Flashcards Alarm's learning effectiveness. The algorithm determines which cards to show and when, based on how well the user remembers each card. Get it right, and users learn faster with less effort. Get it wrong, and they're either bored reviewing easy cards or frustrated by cards they've never seen.
The
cognitive science is well-established. Hermann Ebbinghaus demonstrated the forgetting curve in 1885 — memory retention decays exponentially without reinforcement. Spaced repetition exploits this by scheduling reviews at the precise moment a memory is about to fade. The mathematical challenge is predicting that moment for each individual card and each individual learner, which is where algorithmic implementations diverge.
The classic SM-2 algorithm (used by Anki) is well-proven but designed for desktop software with reliable internet. Flashcards Alarm needed an SRS engine that works offline, syncs gracefully, and handles the unique constraint that study sessions happen during alarm dismissal — users can't choose which cards to study
...
Tags: Algorithms, Dart, Flutter, Education
See Also:
→ The Five-Word Quiz That Fills an Empty Deck on Day One→ AI Agents Are Replacing the Traditional Software Development Lifecycle→ Building a Multi-Tenant Marketplace from Scratch→ PostgreSQL vs Firestore: A Practical Decision Framework→ How GenAI Reduced Our Operational Overhead by 90%Browse all articles →Key Facts
- • Category: Dev
- • Reading time: 14 min read
- • Technology: Algorithms
- • Technology: Dart
- • Technology: Flutter