FSRS Algorithm in Production: How We Built a Spaced Repetition App

FSRS Algorithm in Production: How We Built a Spaced Repetition App
Posted on06 October 2026

Photo by Wouter on Unsplash

Short answer: The FSRS algorithm estimates, for every flashcard, the chance that you can still recall it right now, and books the next review for the moment that chance drops to a target you pick. We built Phosphor, a Flutter spaced repetition app, on top of it. Phosphor runs FSRS-6 on the phone through the open-source fsrs package for Dart, aims for 90% recall (95% in the two weeks before an exam), and sends each reminder at the predicted moment of forgetting rather than at a fixed hour. Here is where the library stopped, where our code started, and what we would change.

Phosphor app icon
Turn any lecture into FSRS flashcards
Phosphor · Android & iPhone · free plan to start

What is spaced repetition?

Spaced repetition is a study method that brings each fact back for review just before you would forget it. Every successful recall pushes the next review further out, so well-known cards appear rarely and shaky ones often. A spaced repetition algorithm, such as SM-2 or FSRS, decides those gaps for every card.

What the FSRS spaced repetition algorithm actually computes

FSRS describes every memory with three numbers. Difficulty is how hard this card is for this person. Stability is the number of days it takes for the chance of recall to fall from 100% to 90%. Retrievability is the chance of recall at a given instant; it decays with time since the last review, more slowly when stability is high.

After each answer the learner grades the card Again, Hard, Good or Easy. FSRS updates difficulty and stability from that grade and schedules the next review for the point where predicted retrievability meets the desired retention. A card you keep getting right comes back after longer and longer gaps. A card you miss returns after a ten-minute relearning step. FSRS-6 also treats the steepness of the forgetting curve as a parameter instead of a constant.

FSRS is designed to fit those parameters to each learner's own review history. The benchmark published by the Open Spaced Repetition project trains on each user's older reviews and tests on their newer ones.

Student in glasses writing notes into an open book at a table by a large window
Photo by Kyle Gregory Devaras on Unsplash

How we wired FSRS into a Flutter spaced repetition app

We did not write a scheduler. The maths is the community's fsrs package, version 2.0.1. Our work was everything around it.

  • State lives on the card. Each flashcard document in Firestore stores its stability, difficulty, FSRS state, learning step and last review time. Grading is one reviewCard call followed by one write.
  • Two retention targets. The default is 0.9. In the 14 days before an exam date it rises to 0.95, so cards return more often while it counts. A deck's own exam date wins over the account-wide one.
  • Fuzzing stays on. A small random spread on each interval means fifty cards learned on one evening do not all fall due on the same morning.

Three things the FSRS library left to us

A curve that moves during the day. The package counts elapsed time in whole days, so two instants six hours apart can get the same retrievability. That suits a daily chart, not ranking cards by how faded they are this minute. We evaluate the curve ourselves with fractional days, R(t) = (1 + factor × t / S)^decay, taking the decay value from the scheduler's own FSRS-6 parameters.

The forgetting moment. A due date is a day; we wanted an instant. Inverting the same curve gives it in closed form: t = S / factor × (target^(1/decay) − 1) days after the last review. At a 0.9 target that is simply the last review plus S days. It is deliberately not the stored due date, which has been rounded to whole days and fuzzed.

A reminder at that instant. The phone works out the moment, because it holds the FSRS state. A Cloud Function keeps what must never ship inside an app: the push provider's send key, the card text (read from Firestore, never accepted from the client), and the rule that each learner has at most one reminder waiting. Reviewing early cancels it. A moment that falls between 22:00 and 08:00 moves forward to 08:00, never backwards, because a reminder that fires before the memory fades says something untrue. The notification carries the four grade buttons.

Hand writing study notes on small white index cards laid out on a dark wooden desk
Photo by Matt Ragland on Unsplash

One pipeline for videos, topics and notes

Phosphor builds cards from a lecture video link, a typed topic or pasted notes. Every input becomes one shape: a source id plus segments, each with a start time, a duration and text, which is what a video transcript already looks like. A topic goes through a language model that writes study material in that shape. Pasted notes are chunked into it with no model call, so pasting uses no generation quota. The flashcard, quiz, summary and concept-map generators read only the source id and its segments, and none of them knows where the text came from. Each card keeps the timestamp of the passage it came from, and generated decks are cached for 30 days.

Trade-offs we accepted

  • Default parameters for everyone. Each card stores its current FSRS state, not a log of every grade. The FSRS optimizer trains on exactly that log, so every Phosphor learner runs on the default FSRS-6 parameters today.
  • A fixed target. Retention is 90% or 95%, with no slider for trading fewer reviews against lower recall.
  • Older cards carry an estimate. The first version used Leitner-style intervals. Those cards switch to FSRS on their next review instead of through a database backfill, so until then their memory estimate comes from the old interval.
  • Machine-written cards are sometimes wrong. A model working from a transcript can produce a vague or incorrect card. Any card failed four times collects in a list where the learner can rewrite, reset or suspend it. Misses on cards the learner had just marked as sure are counted separately, since research on the hypercorrection effect finds confidently held errors respond well to feedback. That count changes no interval.

What we would do differently

  • Log every review from the first release. One row per grade (card, rating, time) is cheap, and it is what per-learner tuning trains on.
  • Start on FSRS. The Leitner-to-FSRS migration code exists only because we began with the simpler scheduler.
  • Alert on fallbacks, not only on failures. Card generation calls a primary model provider with a backup. When the model IDs we had configured were retired, every call quietly failed over to the backup, and nobody noticed until the backup account ran out of balance. A test now checks the configured IDs against the provider's live model catalogue. A fallback that works can hide a primary that does not.

Building a spaced repetition or flashcard app? A checklist

  1. Start from a maintained FSRS implementation for your language instead of inventing intervals.
  2. Choose the retention target, and decide whether learners may change it.
  3. Store per-card FSRS state and, separately, a log of every review.
  4. Decide where time is computed, and keep push keys and message text on the server.
  5. Plan for bad cards and for people leaving. Phosphor exports decks to Anki (.apkg or CSV), Markdown for Obsidian, and PDF.
Phosphor app icon
Turn any lecture into FSRS flashcards
Phosphor · Android & iPhone · free plan to start

Paste a lecture link, a topic or your own notes and Phosphor builds the flashcards. FSRS schedules each card for the day you would forget it, and decks export to Anki (.apkg or CSV), Markdown and PDF.

Install free on Google PlayDownload on the App StoreSee what it does →

Who built it

Teamz Lab is a product studio registered in the UK and run by engineers from Bangladesh. We design and build mobile apps and web products for companies in Bangladesh, the UK and the Nordics, and we ship apps of our own, Phosphor among them, so that what we learn reaches client work.

Building a study app, a flashcard product, or anything with a scheduler at its core?

Tell us what you are planning and how far along it is.

Talk to Teamz Lab

FAQ

What is the FSRS algorithm? FSRS (Free Spaced Repetition Scheduler) is an open-source scheduling algorithm. It models each memory with difficulty, stability and retrievability, and schedules the next review for when predicted recall falls to a target retention. Anki has offered it as an option since version 23.10.

What is the difference between FSRS and SM-2? SM-2 is the older spaced repetition algorithm from SuperMemo, and Anki's original scheduler was built on it. SM-2 multiplies the last interval by a per-card ease factor that each grade nudges up or down. FSRS instead estimates the probability that you still remember the card and schedules the review for when it falls to your chosen retention. In the Open Spaced Repetition project's benchmark, FSRS predicts recall more accurately than SM-2.

How often should you review flashcards? There is no fixed timetable. Review the cards that are due each day and let the algorithm set the gaps. With FSRS at a 90% target, a card's next review comes when its stability, in days, has passed since the last one, so gaps grow from minutes for a new or missed card to days, weeks and then months for cards you keep recalling.

What does a spaced repetition app need besides the algorithm? Somewhere to store each card's scheduling state, a separate log of every review if you want to tune the parameters per learner, reminders that arrive when cards are due, and a way to export decks. In Phosphor the reminder timing runs on the phone, while push keys and card text stay on the server.

Does Phosphor tune FSRS to each learner? No. Every learner runs on the default FSRS-6 parameters, because Phosphor does not keep the per-review log the optimizer trains on.