It started as low-grade irritation with the society I live in.
Everywhere I go, heads are down. Which — fine. Do whatever you like on your own time. But my cab driver doomscrolls Instagram Reels at 60 km/h while I sit in the back quietly re-reading my life insurance policy. The hospital staff watch Reels, loudly, outside the operation theatre. The metro is a carriage of bowed necks and leaking audio, forty people half-present and nobody fully anywhere.
This is not a vibes problem. It has a body count waiting to happen. The distracted driver eventually meets a divider. The distracted nurse eventually misses a handover. The patient on pain management gets to recover to the soundtrack of somebody else's algorithm. What's next — a neurosurgeon in Meta Ray-Bans, resecting a glioma while catching up on his feed between sutures? Efficiency! Multitasking! Very 2026.
The peak of the irritation arrived after I became a father.
Routine check-up. Hospital waiting room. The staff are scrolling. The parents are scrolling. And then the part that actually landed somewhere in my chest: the toddlers are scrolling. Or propped in front of something loud and strobing and expertly engineered, by a system that has never once been asked whether this particular child should be watching this particular thing.
My wife and I had done the reading before that day, so none of it was news. Kids under two learn shockingly little from a screen — researchers call it the transfer deficit, and it shows up in imitation, in language, in emotional learning. A toddler who watches a person stack blocks on a screen just does not pick it up the way a toddler who watches you do it in the room does. Screens for the very young mostly displace the thing that actually works, which is a face.
So: irritated father, engineer's brain, long drive home. And somewhere on that drive I had to admit the uncomfortable part.
I helped build the problem.
Back at upGrad, my friend Adarsh and I built a thing called upGrad Shorts. A TikTok-style vertical feed of short learning videos. It worked, in the way these things work. Decent audience, growing. NPS was fine. Watch time was good. People swiped for a while and said nice things about it in surveys.
Then I looked at the metric that actually mattered.
upGrad's north star is transition rate — did the learner's career actually move after the course? A promotion or a job upgrade is a positive. No change is a negative. It's a brutal, honest metric, because it is completely immune to how much you enjoyed the videos. Transition rate wasn't great before Shorts. Shorts launched. Transition rate stayed exactly where it was.
We had built something people watched. We had not built something people learned from. Those are different products that happen to look identical inside a phone-shaped rectangle.
So we tried again — at a hackathon, which is where all honest engineering happens. Same short-form format, but bolt on SM-2 spaced repetition. Content chunks tagged to a course. A quiz at the end of the session. The quiz result feeds a scheduler, and the scheduler decides what you see next. Remember something well and it goes away for a while. Fumble it and it comes back sooner.
We won. There was a cheque. There was applause. And then the roadmap ate us both and the thing went into the drawer where good hackathon projects go to be quietly composted.
That was five years ago. The drawer has been bugging me ever since, and the hospital waiting room reopened it.
So: can we take the weapons of mass distraction built by our AI overlords and point them somewhere that isn't a seven-year-old's dopamine system?
The idea
Nobody needs another app. I am not going to build another app. The distribution already exists — YouTube Kids, Duolingo, whatever Instagram is calling itself this quarter. Every one of them already has the infra: a vertical player, a recommender, a CDN, an events pipeline. What they don't have is a corner of the product where the goal is different.

So, Phase 0, deliberately unambitious:
- A short-form education section inside an app the kid already uses.
- Topics they actually pick. Marine mammals and their migrations. Constellations. Why Jupiter has more moons than you have opinions.
- A hard session cap. Fifty clips, or twenty minutes, whichever comes first. Then the feed ends. It shows an end screen. There is no next.
- Every clip is tagged to a concept.
- A short quiz at the end of the session.
- The quiz is not a grade. Nobody sees a score. It is fuel for a scheduler.
That's it. Existing platform, existing player, one new table and one new service.
Now, the part that makes this a different product rather than a re-skin:
The most engaging next item and the most educational next item are frequently not the same item.
Every feed you have ever used resolves that tension in favour of engagement, because engagement is what it can measure today and learning is what it could measure in a month if anybody bothered. This one resolves it the other way, on purpose, and eats the watch-time hit.
Why “engaging” is a trap
This isn't a moral position, it's a memory-science one, and it's older than the phone.
Robert and Elizabeth Bjork's New Theory of Disuse splits memory into two quantities that everybody conflates: storage strength (how well-learned a thing is) and retrieval strength (how easily you can get at it right now). The non-obvious bit is that they're independent — and the biggest gains in storage strength happen exactly when retrieval strength is low. Recalling something you'd half-forgotten builds it far more than reviewing something still warm in your hands. The struggle isn't a side effect of the learning. The struggle is the learning. The Bjorks named the design principle that falls out of this: desirable difficulties.
Which means fluency is a liar. When something feels easy — because you watched it forty minutes ago and it's still sitting in the front of your head — your brain reads that ease as I know this. It is usually wrong.
Roediger and Karpicke demonstrated this with some cruelty in 2006. Students who re-read a passage did better on an immediate test than students who practised retrieving it. A week later the ordering flipped hard: the retrieval group held onto roughly 61% of the material, the re-readers about 40%. And here's the knife — the re-readers were more confident. They felt like they knew it. They didn't. Feeling good about material and having learned it are close to unrelated.
Stack that against a watch-time feed and the conflict is total. The engagement-maximising move — serve the familiar, fluent, just-saw-it thing that reliably gets the tap — is precisely the move that manufactures the illusion of competence and produces nothing durable. A short-form learning feed optimised for engagement isn't neutral. It is actively, mechanically anti-learning.
Timing isn't arbitrary either. The spacing effect is one of the sturdiest findings in the whole field — Cepeda and colleagues' 2006 meta-analysis swept 839 assessments across 317 experiments — and their 2008 follow-up with 1,354 participants found the optimal gap between sessions scales with how long you need the memory to survive: roughly 20–40% of a one-week retention interval, dropping to about 5–10% for a one-year one. Want it for a week? Review in a day or two. Want it for a year? Wait weeks. Get the gap wrong in either direction and you've burned a review for nothing.

So the scheduler isn't a nice-to-have bolted onto the feed. The scheduler is the feed.
SM-2
The scheduling core, in the hackathon version, was SM-2 — the 1987 SuperMemo algorithm by Piotr Woźniak. It is older than Python. It predates the web. It runs comfortably on a pocket calculator, and it still holds its own against a distressing number of things with “neural” in the name.
It's almost embarrassingly simple. Each item carries an easiness factor (E-Factor, starting at 2.5). You rate your recall 0–5 after each review. The interval grows by multiplying. The E-Factor update is one line:
EF′ = EF + (0.1 − (5 − q)(0.08 + (5 − q) · 0.02)), EF′ ≥ 1.3
Rate an item easy and the interval stretches. Rate it hard and the E-Factor sags and the item comes back sooner. That's basically the entire algorithm.
from dataclasses import dataclass
@dataclass
class Card:
ef: float = 2.5 # easiness factor
interval: int = 0 # days
reps: int = 0 # consecutive successful reviews
def sm2(card: Card, q: int) -> Card:
"""q in 0..5. q >= 3 counts as recall."""
if q >= 3:
if card.reps == 0:
card.interval = 1
elif card.reps == 1:
card.interval = 6
else:
card.interval = round(card.interval * card.ef)
card.reps += 1
else:
card.reps = 0
card.interval = 1 # back to square one. brutal.
card.ef = max(1.3, card.ef + (0.1 - (5 - q) * (0.08 + (5 - q) * 0.02)))
return cardIn our product the 0–5 grade was never a number the learner saw. It was a swipe — right for “got it”, left for “hard” — plus the quiz result. Nobody wants to self-assess on a six-point scale. Six-year-olds definitely don't.
Where SM-2 falls over, and I'll be honest about it because these weaknesses are what motivated everything after:
- It's a hand-tuned heuristic. Those magic constants — 0.1, 0.08, 0.02, 1.3, 6 — aren't fitted to anything. They're one man's excellent intuition from 1987.
- Every card starts at EF 2.5 regardless of the item or the learner. A card about photosynthesis and a card about the Krebs cycle begin life as equals, which they are emphatically not.
- There's no per-user model. SM-2 knows things about the card. It knows nothing about you.
- Ease hell. The Anki community's term, and it's a real failure mode: repeated lapses drag the E-Factor toward the 1.3 floor and you end up grinding the same card at ever-shorter intervals forever, with no mechanism to climb back out.
- A lapse resets you to a one-day interval no matter how stable the memory was. Forgetting a card you've known for eight months and forgetting a card you learned yesterday are treated identically. They're not the same event.
None of which stops it from being a perfectly reasonable default. It just means someone was eventually going to fit the curve properly.
FSRS
Someone did.
FSRS — the Free Spaced Repetition Scheduler, built by Jarrett Ye and the open-spaced-repetition community — is now the default scheduler in Anki. It grew out of the DHP model from MaiMemo, a variant of the DSR model, and it replaces SM-2's single ease number with three quantities that each mean something:
- D — Difficulty, on [1, 10]. How hard this item is for this learner.
- S — Stability, in days. The interval at which recall probability has decayed to 90%.
- R — Retrievability. Probability you'd recall it right now.
Grades collapse to four: 1 = again, 2 = hard, 3 = good, 4 = easy. Which maps beautifully onto a kids' quiz — wrong, slow-and-right, right, instant-right — without ever showing a child a number.

The actual math
In FSRS-6 the forgetting curve is a power law with a trainable decay:
R(t, S) = (1 + factor · t/S)^(−w₂₀), factor = 0.9^(−1/w₂₀) − 1
The factor term exists purely to guarantee R(S, S) = 90% — that is, stability keeps its definition no matter what decay the optimiser lands on. Invert it and you get the scheduling rule, which is the whole reason this is better than a multiplier:
I(r, S) = (S / factor) · (r^(−1/w₂₀) − 1)
Read that carefully, because r — desired retention — is a knob. It's a product decision expressed as a number. Set it to 0.9 and you get frequent reviews and high retention. Set it to 0.8 and you get fewer reviews and more forgetting. SM-2 has no such knob; you get whatever the multiplier gives you and you like it.
Stability after a successful review:
S'ᵣ = S · (e^(w₈) · (11 − D) · S^(−w₉) · (e^(w₁₀(1−R)) − 1) · w₁₅^[G=2] · w₁₆^[G=4] + 1)
Ugly, but the behaviour it encodes is genuinely elegant. Let SInc = S′ᵣ / S (Anki's “factor”, morally). Three properties fall out:
- Higher D → smaller SInc. Hard material stabilises more slowly. Obviously.
- Higher S → smaller SInc. The better you already know something, the harder it is to know it more. Diminishing returns, built in.
- Lower R → larger SInc. The closer you were to forgetting, the more the successful recall buys you.
Property 3 is the spacing effect. Not bolted on as a heuristic — it falls straight out of the equation. The math and Bjork's psychology agree without anyone having to force them to. That's the moment I stopped wanting to write my own scheduler.
Post-lapse stability, which is where SM-2's crude reset gets replaced with something that has actually thought about it:
S′𝒻 = w₁₁ · D^(−w₁₂) · ((S+1)^(w₁₃) − 1) · e^(w₁₄(1−R))
Forget a card with S = 100 and you land around 3 days. Forget one with S = 1 and you land around 0.3. The system remembers that you used to know it.
And difficulty, with mean reversion — the direct fix for ease hell:
D₀(G) = w₄ − e^(w₅(G−1)) + 1, ΔD = −w₆(G−3), D′ = D + ΔD · (10 − D)/9
D″ = w₇ · D₀(4) + (1 − w₇) · D′
That last line is the whole trick. Difficulty is continuously pulled back toward a sane anchor, so no card can spiral to the floor and stay there. The linear damping term (10 − D)/9 means difficulty moves less as it approaches its ceiling. Ease hell doesn't get patched; it gets designed out.
FSRS-6 fits 21 parameters, whose defaults were trained on roughly 700 million reviews from about 10,000 Anki users. On the open-spaced-repetition benchmark it predicts recall more accurately than SM-2 for about 99.5% of users tested, and simulations put the review savings at roughly a fifth to a third fewer reviews for the same retention.
Full formulas for every version, if you want to implement it: The Algorithm wiki. Gentler version: expertium.github.io/Algorithm.html.
Further up the ladder: what else is out there
I read the scheduling literature properly before writing any of this, and the honest summary is: the fancy methods are real, and they beat well-tuned classical baselines by less than the marketing implies.
| Approach | What it adds over SM-2 | Data needed | Honest verdict |
|---|---|---|---|
| SM-2 (Woźniak, 1987) | Per-item ease heuristic | Almost none | Still a fine default |
| FSRS / DSR (Ye, 2022–) | Fitted forgetting curve, per-card D/S/R, tunable target retention | Big population + your history | Real. Worth it. Use this. |
| Half-Life Regression (Settles & Meeder, ACL 2016) | Learns memory half-life from features | Millions of traces | Great — at Duolingo scale |
| DASH / MCM (Mozer & Lindsey) | Item difficulty + learner ability + study history on a psychological memory model | Moderate | Principled; step-function forgetting is awkward |
| MEMORIZE (Tabibian et al., PNAS 2019) | Optimal review times via stochastic optimal control of point processes | A model of memory | Elegant proof, modest empirical edge |
| RL / KT schedulers (Reddy 2016; DRL-SRS 2024) | Learns a policy over review timing | Large data + a good simulator | Promising, brittle, hard to serve |
A few of these deserve more than a table row.
Half-Life Regression is the one everybody cites, and deservedly. Settles and Meeder modelled a memory's half-life as a log-linear function of features and fit it on Duolingo's trace data, reducing error by 45%+ against several baselines at predicting recall, with a reported ~12% lift in daily engagement in an operational study. Real gains. Also: fit on a volume of interaction data that your Phase 0 education tab will not have for two years.

MEMORIZE is the most beautiful piece of work in the pile. Tabibian and colleagues formulate review timing as stochastic optimal control of temporal point processes and derive an optimal reviewing intensity in closed form. It is genuinely lovely mathematics. Its practical edge over a well-tuned threshold policy is... modest. This is a recurring theme.


Speaking of which — two findings kept me humble and should keep you humble too.
First: Khajah, Lindsey and Mozer showed that the dumb heuristic — review the item whose predicted recall is nearest a fixed threshold — performs only slightly worse than exhaustive policy search. The expensive optimisation barely beats the cheap rule. Which, second, is exactly Bjork wearing a different hat: scheduling a review for the moment recall has decayed to your target retention is engineering a desirable difficulty. Set r = 0.9 and you have operationalised a 1992 psychology paper as a single float.


The reinforcement learning literature is where ambition outruns deployability. Reddy et al. framed review scheduling as a POMDP; more recent work like DRL-SRS uses a Transformer to estimate recall and a deep Q-network to pick intervals. Legitimately interesting, and they report gains. But they need a decent memory simulator to train against — you're optimising a policy against a model of a person, and the model is the weakest link — and a policy that assumes the learner is available exactly when the schedule says is a policy that breaks on first contact with a real weekend.
Hold that last thought. It comes back.
Counter-arguments
Every one of these got thrown at me, mostly by me, usually at 1 a.m. Some of them land.
“Screens for small kids are bad, full stop. You're rationalising.”
Partly true, and the guidance is not ambiguous. The AAP and WHO both say no screen media under 18–24 months except video chat, and about an hour a day of high-quality, co-viewed content for ages 2–5. Notably, the AAP moved in 2026 away from rigid hour caps toward a framework about content, context, co-viewing and conversation — which is a helpful shift, because it says the what and the how matter at least as much as the how long.
My honest answer: this is harm reduction, not a health intervention. If you can keep your kid off feeds entirely, do that, close this tab, you've already won. I'm arguing about what's on the screen that's already in their hands, because that's the fight most parents are actually in.
“You're still building a slot machine. You've just put vitamins in it.”
Fair, and I'd rather concede it than dress it up. It is still a feed. It still uses a ranker. The differences are structural rather than spiritual: the session has a hard ending, there's no autoplay into the next one, and the scheduler will deliberately serve something less immediately fun than it could. That's a meaningfully different machine. It is not a different category of machine, and anyone telling you their engagement product is fundamentally virtuous is selling something.
“Educational short-form already exists. #LearnOnTikTok. It doesn't work.”
Correct, and for a reason that's easy to state: watching is not learning. Guo, Kim and Rubin's analysis of 6.9M MOOC video sessions found engagement collapses past about six minutes — and engagement was the good case. The fluency illusion does the rest. You watch a crisp two-minute explainer on orbital mechanics, it feels lucid, you feel smarter, and you retain approximately nothing.
The delta here isn't the video. It's the retrieval and the schedule. Video is the delivery mechanism; the quiz and the interval are where the learning actually happens. Any version of this without a retrieval step is entertainment with a documentary accent.
“Spaced repetition works for flashcards, not for understanding.”
This is the strongest objection and I don't have a clean answer. The evidence for SR is excellent on declarative material — vocabulary, facts, formulas, named entities. Conceptual transfer and reasoning are much shakier ground, and the field knows it.
So: scope it honestly. Constellations, orbital periods, migration routes, the names and order of things — factual scaffolding that a curious kid can then hang understanding on. Don't claim it teaches critical thinking. It doesn't, and pretending otherwise is how ed-tech earns its reputation.
“The quiz will kill the product.”
Yes. In the short term, absolutely. Friction reduces sessions, reduces watch time, reduces every number the growth dashboard is wired to. That is the trade, made deliberately: you are exchanging engagement for outcome. If your organisation measures only the first one, this feature dies in its first quarterly review, and no amount of clean architecture saves it. This is a political problem wearing a technical costume.
“Attention spans aren't actually shrinking. You're moralising with bad data.”
Partly fair, and I want to be careful here because the popular version is straightforwardly bunk. The “human attention span has dropped to 8 seconds, worse than a goldfish” line traces back to a 2015 Microsoft Canada marketing report citing a firm called Statistics Brain, with no peer-reviewed basis, and it has been debunked repeatedly. The goldfish thing isn't even true about goldfish.
The real research is Gloria Mark's, and it's more interesting: average sustained attention on a single screen has fallen from around 2.5 minutes in 2004 to roughly 47 seconds today. That's a genuine behavioural shift. But it's a story about switching, not about damaged brains. Which is an argument for designing around switching — short sessions, clean endings, state that survives an interruption — not for lecturing anyone about goldfish.
“Who pays for it?”
And here's the one that actually kills it. An education tab that ends is revenue-negative inside an ad-funded app. Every minute it succeeds is a minute not spent in the profitable part of the product. That is not a design flaw in my idea, it is the complete and sufficient explanation for why this doesn't already exist.
Three ways out, none free: regulatory pressure (kids' codes and age-appropriate design rules are tightening globally), a paid tier where parents are the customer rather than the product, or a platform whose business model already is learning — which is why Duolingo is the obvious host and the ad-funded incumbents are not.
The finalised system
The decision
Use FSRS. Don't write your own scheduler. I wrote my own scheduler. Learn from me.
Working through the ladder honestly:
- SM-2 — a fine baseline, and if you shipped it tomorrow you'd get most of the value. But ease hell is real, the lapse handling is crude, and within a year you'd be patching your way toward a worse version of FSRS.
- FSRS — fitted forgetting curve, principled lapse handling, ease hell designed out, open source, benchmarked at scale, and it hands you desired retention as an explicit product lever. The lever alone justifies it.
- HLR — excellent, and needs Duolingo-scale traces you won't have on day one.
- DASH / MCM — principled, but you'd be taking on modelling work FSRS has already done and validated.
- MEMORIZE — gorgeous, modest edge, assumes continuous-time availability that children conspicuously do not have.
- RL / KT — needs a simulator, brittle in production, and the field's own benchmarks (more on this below) say the gains are smaller than the abstracts claim.
But keep a neural component — narrowly, and pointed at a different problem.
Here's the correction to what I built at upGrad. At upGrad I let a classifier tug on the interval itself, which meant a model with modest evidence behind it was overriding a memory model with a century of evidence behind it. That was the wrong seam.
The right seam is this: FSRS tells you when in memory-time. It does not tell you when in wall-clock time. FSRS says “review this in six days.” It has no idea that this kid gets the tablet after dinner on weekdays and disappears entirely into a football pitch on Saturdays. That's not a memory problem, it's an availability problem — and it's a much easier, much better-posed one.
So:
- FSRS owns the interval. When in memory-time.
- A small classifier owns the slot. When in wall-clock time, within the day FSRS picked.
- The classifier never moves the due date. It picks which session the due item surfaces in. If the due date passes unclaimed, FSRS handles the overdue case natively — which, unlike SM-2, it does gracefully, because stability converges to a bound rather than growing linearly with your negligence.
That separation is the actual design contribution here, and it's the thing I got wrong the first time. Remember the RL critique — a policy that assumes the learner is available exactly when the schedule says breaks on contact with real weekends? This is that gap, patched by the smallest model that can patch it, and nothing more.
The hard constraint
Over the top of all ranking sits one non-negotiable rule:
Never surface a concept for review meaningfully before its scheduled date — regardless of predicted engagement.
Engagement affinity may reorder what you see. It may never override the spacing. If the model is certain a kid will love re-watching the whale migration clip they saw yesterday, the system's answer is no.
Serving a “no” at low latency
The interesting engineering problem with a hard constraint is that it's a negative, and negatives sit awkwardly in a two-stage recommender. The standard shape is candidate generation (millions → hundreds) then ranking (score the hundreds), often with a re-ranking stage for diversity via MMR or DPP.
A “never show X before date D” rule looks like a business-rule filter, and the naive instinct is to bolt it on at the very end, after ranking. That's wrong twice over: you burn ranking compute scoring candidates you're forbidden to show, and on a bad day you filter so aggressively that you under-fill the feed and have nothing to serve.
So the constraint lives at retrieval, not at ranking. An item whose scheduled date is in the future is simply never a candidate. The ranker doesn't see it, doesn't score it, can't be tempted by it. New content and due reviews are pulled as separate candidate pools and interleaved at a per-learner ratio, which conveniently also gives you a clean dedup and cooldown boundary.
The original was flag-controlled top to bottom — every scoring component independently toggleable and reweightable per cohort, events into Amplitude, several simultaneous experiments with assignment-level bucketing so they didn't contaminate each other. The stack was deliberately boring: Python with scikit-learn and PyTorch for models, Node/React/TypeScript on the product, Redis and PostgreSQL for state, Lambda for the scoring path. Boring stacks are a feature when the interesting part is the policy.
System design
Drag to pan · Ctrl/⌘+scroll to zoom · % resets to fit · Full for fullscreen
Four things worth pointing at in that diagram:
- The hard constraint sits before the ranker, not after it.
- The availability classifier operates on ordering and timing only — it never touches
due_at. - The session has a terminal state. There is an end screen and nothing after it.
- The quiz result is the only thing that writes back to memory state. Watch time writes nothing. Watch time is not evidence of anything.
FSRS core
import math
from dataclasses import dataclass
# FSRS-6 defaults, trained on ~700M reviews
W = [0.212, 1.2931, 2.3065, 8.2956, 6.4133, 0.8334, 3.0194, 0.001,
1.8722, 0.1666, 0.796, 1.4835, 0.0614, 0.2629, 1.6483, 0.6014,
1.8729, 0.5425, 0.0912, 0.0658, 0.1542]
@dataclass
class Memory:
stability: float # days until R = 0.9
difficulty: float # 1..10
def _factor(w=W):
return 0.9 ** (-1.0 / w[20]) - 1.0
def retrievability(elapsed_days: float, s: float, w=W) -> float:
return (1.0 + _factor(w) * elapsed_days / s) ** (-w[20])
def next_interval(s: float, desired_retention: float = 0.9, w=W) -> float:
"""desired_retention is a PRODUCT decision, not a hyperparameter."""
return (s / _factor(w)) * (desired_retention ** (-1.0 / w[20]) - 1.0)
def update(m: Memory, g: int, elapsed_days: float, w=W) -> Memory:
"""g: 1=again 2=hard 3=good 4=easy"""
r = retrievability(elapsed_days, m.stability, w)
# difficulty, with mean reversion -> no ease hell
d0_easy = w[4] - math.exp(w[5] * 3) + 1
d_prime = m.difficulty + (-w[6] * (g - 3)) * (10 - m.difficulty) / 9
d_new = min(10.0, max(1.0, w[7] * d0_easy + (1 - w[7]) * d_prime))
if g == 1: # lapse
s_new = (w[11]
* d_new ** (-w[12])
* ((m.stability + 1) ** w[13] - 1)
* math.exp(w[14] * (1 - r)))
else: # recall
bonus = w[15] if g == 2 else (w[16] if g == 4 else 1.0)
s_new = m.stability * (
math.exp(w[8])
* (11 - d_new)
* m.stability ** (-w[9])
* (math.exp(w[10] * (1 - r)) - 1)
* bonus
+ 1
)
return Memory(stability=max(0.01, s_new), difficulty=d_new)Mapping a kid's quiz to a grade
The child never sees a rating scale. We infer it, and we use latency because hesitation is signal — a slow correct answer is a memory on its way out.
def to_grade(correct: bool, response_ms: int, median_ms: int) -> int:
if not correct:
return 1 # again
if response_ms > 2.0 * median_ms:
return 2 # hard — right, but it cost them
if response_ms < 0.6 * median_ms:
return 4 # easy — instant
return 3 # goodThe constraint filter
REVIEW_CANDIDATES = """
SELECT concept_id, stability, difficulty, due_at
FROM memory_state
WHERE learner_id = %(learner)s
AND due_at <= %(now)s -- the whole thesis, one predicate
ORDER BY due_at ASC
LIMIT 200
"""
def review_candidates(db, learner_id, now):
rows = db.query(REVIEW_CANDIDATES, learner=learner_id, now=now)
# belt and braces: this must never fire, and if it does I want a page
assert all(r.due_at <= now for r in rows), "early resurfacing"
return rowsThat assert is not decorative. It is the one invariant of the entire system, and I'd rather 500 the request than quietly ship a feed that has started optimising for engagement behind my back.
The availability classifier
Small on purpose. It answers one question: given this learner's history, how likely are they to start a session in slot h? Nothing about memory. Nothing about intervals.
import torch
import torch.nn as nn
class SlotAvailability(nn.Module):
"""P(session start | hour-of-week slot). 168 slots. That's it."""
def __init__(self, n_learners: int, emb: int = 16, hidden: int = 32):
super().__init__()
self.learner = nn.Embedding(n_learners, emb)
self.gru = nn.GRU(input_size=4, hidden_size=hidden, batch_first=True)
self.head = nn.Sequential(
nn.Linear(hidden + emb, 64), nn.ReLU(), nn.Linear(64, 168)
)
def forward(self, learner_ids, recent_sessions):
# recent_sessions: (B, T, 4) = [sin_h, cos_h, dow_norm, duration_norm]
_, h = self.gru(recent_sessions)
z = torch.cat([h.squeeze(0), self.learner(learner_ids)], dim=-1)
return self.head(z) # logits over 168 hour-of-week slots
def pick_slot(logits, due_day_slots):
"""Only ever choose among slots on or after the FSRS due date."""
mask = torch.full_like(logits, float("-inf"))
mask[:, due_day_slots] = 0.0
return (logits + mask).argmax(dim=-1)Note pick_slot. The mask is the contract. The classifier can express any preference it likes and the constraint still holds, structurally, in the type of the operation rather than in someone's discipline.
Cold start, since it's the weakest point in any of these systems: SM-2's flat 2.5 and FSRS's population defaults are both worst on brand-new items, at exactly the moment you have zero signal. An LLM can produce a serviceable difficulty prior from the concept text alone — “photosynthesis, ages 8–10, three sub-steps” is enough to guess that this is harder than “the Moon orbits the Earth.” It's the one place in this design where a language model earns its keep, and it's a place nobody thinks to put one.
Does it work?
The upGrad case study reports a 15% lift in retargeting and cross-sell experiments tied to Shorts, measured by holdout — learners with Shorts disabled versus enabled, conversion to new program enrollment as the primary metric — sustained across multiple experiment cycles.
I want to be precise about what that number is and, more importantly, what it isn't.
It's a business metric. Re-engagement and cross-sell. It is not a measurement of long-term knowledge retention, and I'd be misrepresenting my own work if I let it stand in for one. The mechanism we believed was driving it was the spaced-repetition cadence pulling learners back on a regular rhythm, which handed the Growth team a high-intent, active audience. The fact that it held across cycles is decent evidence that it was the cadence rather than novelty — novelty decays, and this didn't.
But plainly: we never ran the controlled delayed-recall test that would prove the feed made anyone remember more. I believe it did, on the strength of the theory and the shape of the engagement curves. I cannot show you the retention curve, because we didn't measure one.
If I ran this again, the experiment I'd insist on before writing a single line of scheduler:
- Delayed recall, held out. Same content, two arms — schedule-driven versus engagement-driven ordering. Surprise retrieval test at 7 and 30 days, on concepts not in the recent review window. If the constraint doesn't beat the engagement ranker there, the entire thesis is wrong and I'd want to find that out in week six rather than year two.
- Session-count cost, measured and stated up front. The quiz will cost you sessions. Know the number before someone in a review meeting discovers it and frames it as a regression.
- An outcome metric, which is the genuinely hard part. upGrad had transition rate — brutal, lagging, and completely immune to whether you enjoyed the videos. What is the equivalent for a nine-year-old learning about Jupiter's moons? Nobody has a good answer. And the absence of a credible outcome metric is precisely why watch time wins by default: it's the only number available on Monday morning.
That last bullet is not a footnote. It's the whole reason we're here.
A caution about the shiny thing
One more, because it's the most seductive branch and the one I'd most likely have chased at 28.
Knowledge tracing runs from Deep Knowledge Tracing (Piech et al., NeurIPS 2015) — an LSTM over interaction sequences that posted big AUC jumps over Bayesian Knowledge Tracing, roughly 0.86 against 0.69 on ASSISTments — through the self-attention era: SAKT (2019), SAINT/SAINT+ (Riiid, on the 70M-interaction EdNet dataset), AKT (KDD 2020, with a monotonic attention that explicitly models forgetting). Each one beats the last, if you read only the abstracts.
Then the pyKT benchmark (Liu et al., NeurIPS 2022) standardised the evaluation and found two things worth tattooing somewhere visible. First, much of the reported improvement over plain DKT is minimal. Second — and this is the ugly one — a widely used evaluation setup leaks ground-truth labels and inflates AUC, by around 8.4% on ASSISTments2009 and 13% on Algebra2005. Independent reproductions couldn't confirm that SAKT beats vanilla DKT at all, and Gervet et al. showed a well-tuned logistic regression stays competitive with deep KT everywhere except the very largest datasets.
Same lesson as the schedulers, one rung up the ladder: the sophisticated thing wins by less than its title suggests, and occasionally it wins only because the evaluation was broken. Ship FSRS. Run KT as an experiment with a control you trust. Not the other way around.
Conclusion
The paper that gave us all of this was called Attention Is All You Need. It described a mechanism inside a model for deciding which parts of an input deserve weight. Nine years later, the same idea, industrialised, decides which parts of the world reach my son.
That's the joke, and it isn't especially funny. Attention really is all you need — it's the substrate under every single thing a person will ever learn — and we have spent a decade building extraordinarily capable machinery for spending it as fast as possible, on nothing, at a rate of 47 seconds a hop.
None of what I've described here is hard. SM-2 is a 1987 algorithm you could implement on a napkin. FSRS is open source, benchmarked to death, and free. The retrieval filter is one predicate in a WHERE clause. The availability model is a GRU with an embedding table and a Tuesday afternoon of work. The whole thing fits inside infrastructure that four different companies already own and operate at planetary scale.
The hard part was never the engineering. The hard part is that nobody's compensation is tied to whether a nine-year-old remembers what a moon is. Watch time has a dashboard. Learning has a research budget and a two-year lag. Given those incentives, every ranker on earth converges to the same answer, and it isn't a conspiracy — it's just a gradient, and everyone is standing on it.
So the design is really one sentence long, and everything else in this post is implementation detail:
Build a feed that will, when it matters, refuse to give you what you want right now — because it is trying to give you something you'll be glad you have later.
That's it. A hard constraint, an ending, and a question at the end.
Will I build it? I don't know. It needs a platform, and the platforms have no reason to want it until a regulator or a paying parent gives them one. But I'm no longer willing to pretend the default is neutral, because I've now sat in a hospital waiting room and watched what the default does to a room full of toddlers.
We won a hackathon for this five years ago and put it in a drawer. The drawer has been open for a while now.

Research appendix — sources & key findings
Primary source (my own work)
- upGrad Shorts case study — micro-learning feed with SM-2 spaced repetition and a neural classifier predicting optimal review intervals per learner; 15% lift in retargeting and cross-sell experiments. Source of the constraint, swipe-grade UX, urgency/novelty/affinity blend, stack, Amplitude harness, and Growth positioning.
- upGrad LMS rebuild — tenure Dec 2019–Sept 2021, 3M+ learners, Tier 2/3 India context.
- Work index — dates Shorts to 2020.
Learning science
- Bjork & Bjork, A New Theory of Disuse (1992) — link
- Bjork & Bjork, desirable difficulties — PDF
- Roediger & Karpicke, Test-Enhanced Learning (2006) — link
- Cepeda et al. (2006) meta-analysis — PDF; Cepeda et al. (2008) spacing ridgeline — PDF
- Guo, Kim & Rubin, video engagement (L@S 2014) — ACM
Schedulers
- SM-2 — SuperMemo archives; algorithm help
- FSRS formulas — The Algorithm wiki; explainer; benchmark
- Settles & Meeder, HLR (ACL 2016) — PDF
- Khajah, Lindsey & Mozer threshold heuristic — PDF
- Tabibian et al., MEMORIZE (PNAS 2019) — link
- Reddy et al. (2016) — arXiv; DRL-SRS (2024) — MDPI
Kids, screens, and the attention myth
- AAP 2026 digital media guidance — CHOC summary
- Transfer / video deficit — APA Monitor
- Attention-span myth debunked — link; Gloria Mark / APA — speaking of psychology
Recommender systems & knowledge tracing
- ByteDance Monolith — arXiv; Covington et al., YouTube recommendations — PDF
- Stray et al. on values in recommenders — 2021, 2022
- Piech et al., Deep Knowledge Tracing — PDF; pyKT — PDF; Gervet et al. — PDF
On this site
- The Nutritionist in the Machine — another recommender with hard constraints that refuses to optimise for engagement alone.
- One Model to Rule Them All — when trees beat titans on structured prediction problems.

