← Back
Home › Systems › Consumer applications

spaced-repetition

Logs every answer a learner gives, estimates how likely each item is still remembered, and shows it again just before that likelihood falls below a target.

Anki, Duolingo

Open the spaced-repetition pagespaced-repetition d-ary heapdata-structures: Array heap with d children per node (d = 4 typical): half the binary heap's depth, siblings in one cache line, faster priority queues.›Incremental sync cursorsdistributed: The client keeps one opaque token; each pull returns only the changes after it plus the next token, so sync cost scales with what changed, not with the dataset.›Linear and logistic regressionmachine-learning: Fit a weighted sum of features to a number (least squares) or a probability (logistic); the baseline every model must beat.›Spaced repetitionmachine-learning: Review scheduling by memory models (SM-2, FSRS) predicting recall probabilityL-BFGSnumerics: Quasi-Newton minimizer that approximates the inverse Hessian from the last m gradient differences via the two-loop recursion; O(m·n) memory and work per step.›A/B testing (online controlledexperiments)statistics: Randomly split units into arms by a salted hash, log exposure, compare metrics with a confidence interval, and guard the result with sample-ratio, guardrail and peeking checks.›Item response theorystatistics: Answer probability is a logistic curve of ability minus item difficulty (1PL), with item slope (2PL) and a guessing floor (3PL); fit items by marginal ML, score people, measure precision by information.›B+ treestorage: Balanced high-fanout search tree with all values in linked leaves; the default on-disk index for point lookups and range scans.›Write-ahead logstorage: Append each change to a checksummed sequential log and fsync it before touching data pages; the basis of crash durability in nearly every database.›Calendar date arithmeticsystems: Civil dates, time zones (tzdb), DST gaps, month-end anchoring and duration mathMessage sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›Cursor paginationnetworking: Keyset/opaque-cursor pagination for stable incremental reads from APIs and tablesLoss functionsmachine-learning: The scalar a model minimizes: cross-entropy for classes and tokens, MSE or Huber for values, each with a stable form and simple gradient.›LU and Cholesky factorizationnumerics: Factor A = PLU (any square matrix, partial pivoting) or A = LLᵀ (SPD) once in O(n³), then solve each right-hand side in O(n²).›Bootstrap confidence intervalsstatistics: Resampling-based confidence intervals for arbitrary statisticsCUPED (variance reduction withpre-experiment covariates)statistics: Subtract the part of each unit's outcome that a pre-experiment covariate predicts; the effect estimate stays unbiased and its variance shrinks by 1 - rho^2.›Experiment layering(overlapping experiments)statistics: Partition the tunable parameters into layers, hash each unit with a per-layer salt so layers are independent, and give experiments inside a layer disjoint bucket ranges.›Hypothesis testingstatistics: Pick a test statistic, compare it to its distribution under "no effect", read off a p-value, and control error rates across one test or many.›Sequential hypothesis testingstatistics: Group-sequential and alpha-spending designs for interim analyses without inflating errorDeterministic bucketingsystems: Hash a stable unit id with a per-purpose salt into a bucket; "in" means bucket below the percentage, so assignment is stateless, repeatable and only grows on ramp-up.›CRC32C checksumshardware-os: The Castagnoli CRC-32 with dedicated instructions on x86 (SSE4.2) and ARMv8. It is the standard fast integrity check for storage blocks, logs and network frames.›fsync, O_DSYNC and O_DIRECThardware-os: What fsync, fdatasync, O_DSYNC, O_DIRECT and F_FULLFSYNC really guarantee on Linux and macOS, and the exact call sequences for crash-safe files.›Wilson score intervalstatistics: Wilson lower bound for ranking by up/down votes and proportions with few samplesBayesian rate estimationstatistics: Estimate a conversion or event rate from few observations with a Beta-Binomial or Gamma-Poisson update: two counters, an honest interval, and P(B beats A) directly.›Sequential probability ratioteststatistics: Add up the log-likelihood ratio per observation and stop when it crosses a fixed bound; the least data of any test with the same error rates (for two point hypotheses).›SIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›Loglikelihood scoringmachine-learning: Scoring choices by summed token log-probabilities, normalization by length, perplexity computation

How it is built

Architecture overview

A spaced-repetition system keeps a log of every time a learner answered an item, turns that log into an estimate of how likely the learner is to still remember the item, and shows each item again just before the estimate falls below a target (for example 90 %). Four things make up the system: a store of items and the review log, a memory model that predicts recall for one item at one moment, a scheduler that turns the prediction into a due date and builds today's queue, and a trainer that fits the model's parameters to the review log. Around them sit sync (the same collection on phone and laptop) and experiments (does the new model actually help?). The one idea that makes it work: the interval is not a fixed ladder (1 day, 3 days, 7 days) but a decision from a forgetting curve, and the curve is learned from the learner's own history. Two well-documented designs share this skeleton: Anki, where the collection and the scheduler live on the user's device and the parameters are fitted per user; and Duolingo's half-life regression, where one model is fitted on all students and run by a server.

Parts:

 learner ──answer (item, grade, time)──► review UI
                                           │ 1 row per answer
                                           ▼
   ┌───────────── store (SQLite / DB: WAL + B+ tree indexes) ──────────────┐
   │  items/cards (state, due, memory state)      review log (append-only)  │
   └──────▲───────────────────────┬──────────────────────────┬─────────────┘
          │ new state + due       │ due ≤ now                │ whole log
   ┌──────┴───────┐        ┌──────▼───────┐           ┌──────▼───────┐
   │ memory model │◄params─┤ scheduler:   │           │ trainer      │
   │ (R, S, D or  │        │ queue, limits│           │ (offline fit)│──► new params
   │  half-life)  │        │ day rollover │           └──────────────┘
   └──────────────┘        └──────────────┘
          ▲  sync: changes after a cursor  ▼          experiments: arm per learner
       device A ◄──────────────────────► server ◄──────────────────► device B

How the parts fit together

Memory model

The model answers one question: given what happened to this item before and how long ago, what is the chance the learner recalls it now? The block spaced-repetition (no page yet) is meant to cover the family; here is how the documented members differ.

  • SM-2 (SuperMemo, 1990) is a rule, not a fit: after the n-th successful repetition the interval is I(1)=1, I(2)=6, I(n)=I(n-1)·EF, and the easiness factor EF is updated from the answer quality q (EF' = EF − 0.8 + 0.28q − 0.02q²), never below 1.3 (source: the SM-2 description at super-memory.com). Cheap and predictable, but it has no notion of "how likely is recall right now", so a card reviewed late is treated like one reviewed on time.
  • Half-life regression (HLR) (Settles and Meeder, ACL 2016) predicts recall as p = 2^(−Δ/h), where Δ is the time since the last practice and the half-life h = 2^(θ·x) is a learned linear function of features: counts of times seen, correct and wrong, plus an indicator per lexeme tag (about 20 thousand of them), which acts as a per-item difficulty. The paper bounds h to between 15 minutes and 9 months.
  • FSRS (Free Spaced Repetition Scheduler) keeps three numbers per card: retrievability R (chance of recall now), stability S (days for R to fall from 100 % to 90 %) and difficulty D (how hard it is to raise S); the current version, FSRS-6, has 21 parameters (source: the FSRS "ABC" wiki). Anki ships it since 23.10.

Where Item response theory fits: both HLR's per-lexeme weight and FSRS's D are item difficulty, and a learner's overall parameter fit is ability. IRT is the older, cross-sectional version of the same two-sided idea, and is the right tool when you also want to place new items by difficulty before anyone has reviewed them (reasoning, not a claim about either product). The fitting of HLR is Linear and logistic regression in spirit: a smooth loss over a linear predictor, trained on log data.

Contract with the scheduler: the model returns (recall probability at time t) and (the time at which recall reaches the target). The scheduler never looks inside.

Scheduler and queue

The scheduler turns the model's output into a due date, then into a queue. The levers a product exposes are few and worth copying:

  • Desired retention. In Anki, the most important FSRS setting; the default is 90 %. The manual notes that workload rises quickly above 90 % and can become overwhelming above 97 %.
  • Short steps before the first long interval. Anki recommends learning and relearning steps shorter than one day when FSRS is on (for example 10 minutes), so a step never straddles the day boundary.
  • Daily limits and sibling burying. New cards per day and review cards per day cap the load; "bury siblings" delays other cards from the same note until the next day (Anki manual).
  • Load balancing and fuzz. Anki's scheduler spreads due dates so that a big batch learned on one day does not come back as one big batch; the code constrains the choice to a window and caches workload per day for up to 90 days of interval plus 10 % (source: load_balancer.rs in the Anki repo).

For the queue itself, a single learner's due list is small enough for a plain index query. The d-ary heap matters where a service ranks many candidates by predicted recall (pick the items with lowest R for this learner's next session, as Duolingo does when ranking target words for a practice session) because a bounded heap gives the top k without sorting the rest. That is a design choice, not something Anki does.

The day boundary is a real contract, and calendar-date-arithmetic (no page yet) is the missing block that describes it. Anki counts "days elapsed" from the collection's creation and moves to the next day at a configurable rollover hour (for example 4 a.m.), so someone studying at 1 a.m. is still on yesterday's day; a step that crosses the boundary becomes a whole number of days (source: timing.rs in the Anki repo; the manual's example converts a 6-hour delay into 1 day when the next day starts in 5 hours). Time zones and daylight saving make "one day later" ambiguous, so store absolute timestamps for steps under a day and day numbers for the rest.

Review store

Both products keep the data in a relational store, and the review log is the one thing you must never lose: it is the training set for the model and the audit trail of the scheduler. Anki's collection is SQLite, opened with journal_mode = wal and exclusive locking (source: storage/sqlite.rs), which is the Write-ahead log contract: a crash during a review leaves either the old or the new state, never half. Lookups ("cards due today in this deck") hit B+ tree indexes on due date and card state. Ownership: the review UI is the only writer; the scheduler writes the new card state and appends one log row in the same transaction, so card state and log can never disagree.

Trainer

Training fits model parameters to the log. HLR is fitted with AdaGrad (an online gradient method) on a smooth loss (p − p̂)² + α(h − ĥ)² + λ‖θ‖² (source: the HLR paper). The released data set holds 13 million Duolingo learning traces (source: the HLR repository README). Anki's FSRS "Optimize" button fits the parameters to the user's own review history and needs data to learn from: the manual names fewer than a few hundred reviews as a common reason for poor fits. L-BFGS is the natural solver when the fit is small, dense and smooth (a few dozen parameters, full-batch), while AdaGrad-style online methods suit very large sparse feature sets; neither product page above claims L-BFGS, it is the textbook choice for the second case.

Contract between trainer and scheduler: parameters are a versioned, small vector. A change of parameters can re-compute the due dates of existing cards from their history. Anki makes this an option, "Reschedule cards on change", which is off by default: future reviews use the new scheduling but today's workload does not jump. The manual does not recommend switching it on when first moving from SM-2, because many cards can become due at once, and suggests a backup first.

Sync

A learner reviews on a phone on the train and a laptop at home. Incremental sync cursors is the block that keeps this cheap: every row carries an update sequence number (USN), the client remembers the last USN it saw, and a sync sends only rows changed after it. Anki's Rust sync code shows the pieces: pending_usn, server_usn, chunked cards, notes and review-log rows, a finish step that increments the server USN (source: rslib/src/sync/collection/*.rs). Rows deleted since the last sync travel as "graves". The server can be self-hosted (Anki manual, "Self-Hosted Sync Server").

Writes made offline go through the pattern of Offline mutation queue: apply locally at once, send later. The review log makes this safe, because each review is an append: two devices that both reviewed the same card produce two log rows, and the card's state can be recomputed from the merged log.

Experiments

"Is the new scheduler better?" is an A/B test, and it is harder than usual because the outcome (what the learner remembers) shows up weeks later and the learner's behaviour changes with the scheduler. Duolingo assigned students at random to HLR or to the Leitner system it used before; the experiment ran six weeks with just under 1 million students (source: HLR paper). The paper reports retention per activity type for two experiments, and the results are mixed: not every activity type improved, and not every difference was significant. Read the paper's results table, not only the abstract.

Data and control flow

One review (device):

  1. The queue gives the next due card; the UI shows the question and starts a timer.
  2. The learner grades the answer (Anki: Again, Hard, Good, Easy). Anki's manual warns against pressing "Hard" for a failed recall, because FSRS then assumes the answer was correct.
  3. The memory model updates the card's state from the grade and the elapsed time and returns the next due time.
  4. One transaction writes the new card state and appends a review-log row (WAL commit). Time here is microseconds to milliseconds (order of); nothing waits for the network.
  5. The queue shows the next card. Sync runs later, in the background.

Building today's queue:

  1. At the rollover hour the day counter increases; cards with due day ≤ today become due.
  2. Limits apply: new cards per day, reviews per day, burying of siblings.
  3. Ordering rules decide the sequence (FSRS offers "ascending retrievability", which shows the cards most likely forgotten first).

One training cycle (nightly, or on demand):

  1. Read the review log (the whole history for one learner, or a sample across learners).
  2. Fit the parameters; compare the new fit against the old on held-out reviews.
  3. If better, publish the parameter version; reschedule or apply to future reviews only.
  4. Sync carries the new parameters to other devices as part of the preferences.

Scaling

In the order you would pull the levers:

  1. A single learner is tiny. Thousands of cards and tens of thousands of log rows fit in memory; the cost is interface latency, not compute. Keep the review path to one local transaction.
  2. Sync size grows with changed rows, not with the collection, as long as every table has a USN. The first sync and the "full upload" fallback are the expensive paths; chunk them.
  3. Trainer cost grows with log length. Fit per learner when logs are long enough; otherwise fall back to default parameters trained on a pooled data set (the role of the 13-million-trace data set in HLR).
  4. Many learners on one service. Move the memory model behind a server, precompute features (counts per item) incrementally rather than from the raw log, and rank candidates with a heap. The paper bounds HLR's inputs to features "available in the production system without adding latency".
  5. Workload spikes. Load balancing and desired retention are the controls: the Anki manual warns that workload rises quickly above 90 % retention and can become overwhelming above 97 %, so the target, not the code, sets the daily load.

Failure modes

FailureSymptomCauseFix
Review pile-uphundreds of due cards after a break, the learner quitshigh desired retention, no daily cap, scheduler treats late reviews as on timecap reviews per day, use a model that handles delay (retrievability-aware), allow postponing
Day-boundary bugcards due "tomorrow" appear twice, or vanish for travelerslocal midnight vs rollover hour, DST, time-zone changestore UTC instants for short steps, day numbers from a fixed epoch plus rollover hour
"Hard" misuseintervals far too long for forgotten cardsfailure graded as Hard, model reads it as a recalllabel the buttons clearly, document it (Anki manual does)
Overfit optimizerwild intervals right after Optimizetoo few reviews for the parameterskeep defaults below a minimum count, regularize toward defaults
Sync conflictreview lost, or a card duplicatedtwo devices changed the same row, last writer winsappend-only log, recompute card state from merged log
Log lossscheduler cannot be retrainedlog row written outside the card-state transaction, or prunedone transaction for both, never delete log rows
Misleading experimentheadline gain does not holddifferent metric in different arms, novelty effectpre-register the metric, report per-experiment tables, run for weeks

Real implementations

namewhat it isrepolicencenotes
Ankidesktop and sync-server spaced-repetition app, SQLite collection, Rust coreankitects/ankiAGPL-3.0-or-later (with BSD-3 parts)scheduler, sync, FSRS integration in rslib/
fsrs-rsRust implementation of FSRS: optimizer, scheduling, simulationopen-spaced-repetition/fsrs-rsBSD-3-Clauseused by Anki's scheduler (use fsrs::NextStates)
fsrs4ankithe original FSRS project and wikiopen-spaced-repetition/fsrs4ankiMITalgorithm description and optimizer in Python
py-fsrs / ts-fsrsFSRS schedulers for Python and TypeScript appspy-fsrs, ts-fsrsMITfor building your own app around FSRS
halflife-regressionDuolingo's released HLR code and 13M-trace data setduolingo/halflife-regressionMITPython implementation and baselines from the paper
Mnemosyneopen-source flashcard program with its own sync protocolmnemosyne-proj/mnemosynemixed (LGPL-3 for sync, see repo)a second, independent design

Duolingo's production system is not open source; only the model, code and data of the 2016 paper are public.

Sources

  • B. Settles, B. Meeder, A Trainable Spaced Repetition Model for Language Learning, ACL 2016, pages 1848-1858. paper · code and data
  • P. Wozniak, Application of a computer to improve the efficiency of learning (SM-2 description, 1990). super-memory.com
  • Open Spaced Repetition, ABC of FSRS (memory state R, S, D; 21 parameters in FSRS-6). wiki
  • Anki manual: deck options and FSRS, self-hosted sync server.
  • Anki source: rslib/src/storage/sqlite.rs (WAL), rslib/src/scheduler/timing.rs (rollover), rslib/src/scheduler/states/load_balancer.rs, rslib/src/sync/collection/ (USN, chunks), in ankitects/anki.