Building a review schedule that actually sticks.
A short build log: what spaced repetition says about forgetting, and the small scheduler that came out of it.
On this page
Most study plans fail in the second week. The material is not harder. The plan simply asks you to remember too much, too often, with nothing telling you what to look at today. This is a short log of a small scheduler that fixes that one problem.
The problem with re-reading#
Re-reading feels productive because everything looks familiar. Familiar is not the same as recalled. If you can recognise a page but cannot reproduce it from a blank sheet, the page has not been learned yet.
What forgetting looks like#
A simple model says memory decays exponentially. The chance of recalling a card after days is
where is the stability of the memory, in days. Each successful review makes larger, so the next gap can be longer. A failed review shrinks it again.
If we want to review when recall has dropped to a target of = 0.9, we solve for :
So a card with a stability of 20 days comes back in about two days. That is the whole idea behind the schedule.
The rules I settled on#
After a few weeks of trying things, the rules fit in a short table.
| Result of the review | Stability change | Next review |
|---|---|---|
| Forgot it | S × 0.5 (minimum 1 day) | Tomorrow |
| Hard | S × 1.2 | About 1 day later than last time |
| Good | S × 2.0 | Based on the formula above |
| Easy | S × 3.0 | Based on the formula above |
The scheduler#
The core is one small function. It takes a card and a grade and returns the updated card.
type Grade = "forgot" | "hard" | "good" | "easy";
type Card = { stability: number; due: Date };
const factor: Record<Grade, number> = { forgot: 0.5, hard: 1.2, good: 2, easy: 3 };
export function review(card: Card, grade: Grade, now = new Date()): Card {
const stability = Math.max(1, card.stability * factor[grade]);
const days = Math.max(1, Math.round(-stability * Math.log(0.9)));
const due = new Date(now.getTime() + days * 86_400_000);
return { stability, due };
}Two details matter. The minimum of one day stops a card from coming back within minutes. And the function never mutates its input, which makes it easy to test and to undo a mis-tap.
How a session flows#
A study session is a loop with one decision in it.
Loading diagram…
Diagram source
graph LR
A[Open today's queue] --> B[Show the question]
B --> C[Try to answer from memory]
C --> D{Grade yourself}
D -->|Forgot| E[Back tomorrow]
D -->|Hard or good| F[Longer gap]
D -->|Easy| G[Much longer gap]
E --> H{More cards due?}
F --> H
G --> H
H -->|Yes| B
H -->|No| I[Done for today]What a day looks like#
On a typical day the queue holds a mix of new cards and returning ones. The picture below is the card layout I use: question on the front, a short answer and one example on the back.
A few habits keep the queue small:
- Write one idea per card.
- Phrase the front as a question, not a topic.
- Delete cards you no longer care about.
- Stop at a fixed number of new cards per day.
Listen, watch, share#
Some ideas travel better as sound or video. These components load nothing from other sites until you press play.
Posts can sit in the article as a small card, with a live version one tap away.
What I would change next#
The model treats every card alike, but some ideas are simply harder. A per-card difficulty value would help, and so would a way to see the whole curve for a deck. Both are small additions to the same function.