The Mathematics of Learning
duiduidui! is built on a unified mathematical framework combining Bayesian inference, information theory, and cognitive science. It starts with a difficulty scale rooted in the structure of Chinese itself, then measures everything (your knowledge, your skill, your cognitive load) on that same scale.
1. The Difficulty Hierarchy
Every character, word, and phrase in the app has a difficulty score: a single number that tells the system how hard it is. These scores aren't arbitrary: they follow the compositional structure of Chinese itself. Simple characters get low scores, and when characters combine into words and phrases, the difficulty of the combination is calculated from the difficulty of its parts. This creates a natural learning progression from easy building blocks to complex expressions.
This difficulty scale is the foundation of everything else in the system. A user with a skill level of N should be more likely than not to have mastered any item with a difficulty score of N or lower. Every model described on this page (proficiency tracking, skill estimation, cognitive load, card selection) operates on this same scale.
Single Characters
The foundation. Every character has an individually curated difficulty score, and none of them is ad hoc: every score is measured against a scoring rubric we developed, which fixes the system's foundational principles. The rubric's central question for every placement is "what does learning this character let the learner do that they couldn't do before?" Complexity, usefulness, and real-world frequency all weigh in, but, in the rubric's words, "corpus frequency is not the same as pedagogical priority." Examples:
| Character | Pinyin | Meaning | Difficulty |
|---|---|---|---|
| 对 | duì | correct | $1$ |
| 一 | yī | one | $2$ |
| 二 | èr | two | $3$ |
| 三 | sān | three | $4$ |
| 你 | nǐ | you | $5$ |
| 好 | hǎo | good | $6$ |
That 对 (duì, "correct") holds the very first rung is a deliberate choice, twice over: it's one of the handiest words a learner can own from day one (it's how you agree, confirm, and understand your teacher's feedback), and it's the word our app is named after! It's also a useful reminder of what this ladder is: one possible pedagogy among many. We designed this one and we think it's good, but it isn't authoritative, and we keep iterating on it, updating the data and the techniques as we learn from how our users actually learn.
Multi-Character Phrases
When characters combine into a phrase, the phrase's difficulty is always higher than its hardest character, but not as hard as simply adding all the character difficulties together. This makes intuitive sense: knowing the individual characters gives you a head start on the phrase, but there's still something extra to learn about how they work together.
Formally, a compound $P$ decomposes into segments $S_1, S_2, \ldots, S_n$: a word decomposes into its characters, and a longer phrase into its constituent words, recursively. Difficulty is calculated using the ceiling of the root-sum-of-squares of the segment difficulties (using each segment's override where one exists):
$$\text{difficulty}(P) = \left\lceil \sqrt{\sum_{i=1}^{n} \text{difficulty}(S_i)^2} \;\right\rceil$$This formula has several desirable properties:
- Strict monotonicity: Phrases are always harder than their hardest component, guaranteed by the ceiling operation.
- Subadditivity: $\text{difficulty}(P) < \sum_i \text{difficulty}(S_i)$: the whole is easier than the sum of its parts.
- Component sensitivity: Every constituent segment influences the final difficulty.
Worked Examples
| Phrase | Components | Calculation | Difficulty |
|---|---|---|---|
| 你好 (nǐhǎo, hello) | 你(5), 好(6) | $\lceil\sqrt{25+36}\rceil = \lceil 7.8 \rceil$ | $8$ |
| 很好 (hěnhǎo, very good) | 很(7), 好(6) | $\lceil\sqrt{49+36}\rceil = \lceil 9.2 \rceil$ | $10$ |
| 不好 (bùhǎo, bad) | 不(9), 好(6) | $\lceil\sqrt{81+36}\rceil = \lceil 10.8 \rceil$ | $11$ |
| 好吃 (hǎochī, delicious) | 好(6), 吃(12) | $\lceil\sqrt{36+144}\rceil = \lceil 13.4 \rceil$ | $14$ |
| 你很好 (nǐhěnhǎo, you're very good) | 你(5), 很好(10) | $\lceil\sqrt{25+100}\rceil = \lceil 11.2 \rceil$ | $12$ |
The last row shows the recursion at work: 你很好 doesn't decompose into three raw characters but into the word segments 你 (nǐ) and 很好 (hěnhǎo), so the already-computed difficulty of 很好 enters the sum.
When Composition Breaks Down: Difficulty Overrides
The computed score answers one precise question: when does the learner know enough characters to read this item? That's the right default, and our scoring rubric names it the iron rule: a word can never be scheduled before its hardest character, because until then the learner literally can't read it. But language is not perfectly compositional, and the formula fails in two directions. Both failures are corrected by a curated difficulty override on the compound record, judged against the same rubric.
Some compounds are harder than their parts. Knowing the characters doesn't always mean you can guess the word. 个子 (gèzi, "height, stature") is built from "unit" and "child"; 五一 (wǔyī, "Labor Day") requires knowing that May 1st is a holiday; and classical constructions like 而去 (ér qù, "and depart") use beginner characters in literary grammar a modern learner meets much later. There's also a subtler failure mode we call the 奶 principle: because everyday characters like 奶 (nǎi, milk) and 水 (shuǐ, water) are deliberately scheduled early, the formula computes low scores for every combination of them, including 奶水 (nǎishuǐ, breast milk), which no first-week learner needs to discuss with a hotel concierge. These all get overridden upward.
A few compounds are needed before their parts. Downward overrides are rarer and face a high bar: not "is this word common?" but "does the learner need it before they can read its characters?" Survival vocabulary like 学生 (xuéshēng, student) computes far into the intermediate range from its components but is overridden down to the beginner tier, because learners acquire it whole in their first weeks. Phonetic loanwords like 咖啡 (kāfēi, coffee) get the same treatment: their characters are just sound-carriers, so decoding them is beside the point. A downward override deliberately relaxes the monotonicity guarantee above, and that's the honest choice: the learner really does know the word before its parts.
Overrides live on the compound record, never on the characters, so each character's score stays an honest measure of its standalone learnability. Overrides are still the exception: most words are simple, productive compositions of their characters, and the computed score serves them exactly as it is. But the exceptions aren't exactly rare, either. That's just how the language works: less than 20% of words and 4% of phrases carry an override, and everything else stands on the computed score.
Radicals
Radicals are the recurring building blocks inside characters, like how the "water" radical (氵) appears in 河 (hé, river), 海 (hǎi, ocean), and 湖 (hú, lake). Learning radicals helps you recognize patterns and guess meanings, but they're abstract concepts that only make sense once you've seen enough real characters. So the app waits until you've built a solid foundation of characters before introducing them, ensuring you have plenty of concrete examples to anchor each radical's meaning.
Radical difficulty is calculated to favor common radicals early:
$$\text{difficulty} = \max\!\Big(500,\; 500 + \frac{d_3}{\sqrt{c}} + \frac{c_{\max} - c}{10}\Big)$$where $d_3$ is the difficulty of the 3rd-easiest character containing the radical, $c$ is the number of characters referencing it, and $c_{\max}$ is the count of the most-referenced radical. Common radicals surface earlier; rare ones wait.
2. Bayesian Proficiency Model
Think of each flashcard as having a hidden "knowledge meter" somewhere between 0% and 100%. When you first encounter a card, the app genuinely doesn't know whether you'll get it right; you could be anywhere on that scale. With each review, it watches how you do and nudges its estimate accordingly: get it right, and the meter climbs; get it wrong, and it dips back down. Over many reviews, the app builds an increasingly accurate picture of what you actually know.
Mathematically, we model your knowledge of each Chinese phrase as a Bernoulli random variable with unknown parameter $\theta \in [0,1]$, representing the probability you'll correctly recall it. We place a Beta prior on $\theta$:
$$\theta \sim \text{Beta}(\alpha, \beta)$$The Beta distribution is the conjugate prior for the Bernoulli likelihood, which means every review produces an elegant closed-form update:
$$\theta \mid \text{data} \;\sim\; \text{Beta}(\alpha + s,\; \beta + f)$$where $s$ is the number of successes and $f$ the number of failures. The expected proficiency at any point is:
$$\mathbb{E}[\theta] = \frac{\alpha}{\alpha + \beta}$$This Beta distribution carries two pieces of information at once, and the distinction matters for everything that follows. Its mean, $\mathbb{E}[\theta]$, is our belief about how likely you are to recall the card. Its precision, $\alpha + \beta$, is our confidence in that belief. A high mean with low precision means "we think you know this, but we're not sure", a state that turns out to be central to the implied-evidence model in §4.
Review Score Updates
After each review, you tell the app how it went: Easy, Good, Hard, or Fail. Each response provides a different amount of evidence. Nailing an "Easy" is a strong signal that you know the material. Tapping "Hard" is a gentle hint that you're still working on it. Here's how each outcome shifts the model:
| Outcome | $\Delta\alpha$ | $\Delta\beta$ | Interpretation |
|---|---|---|---|
| EASY | $+1.0$ | · | Strong evidence of knowledge |
| GOOD | $+0.7$ | · | Moderate evidence of knowledge |
| HARD | · | $+0.3$ | Slight evidence of difficulty |
| FAIL | · | $+1.0$ | Strong evidence of non-knowledge |
Conservative Convergence
The system is deliberately conservative: it won't decide you've mastered something after just one or two correct answers. Mastery is earned through consistent performance over time. This means a lucky guess won't fool the system, but genuine knowledge will always be recognized:
| After $n$ EASY reviews | Distribution | $\mathbb{E}[\theta]$ |
|---|---|---|
| $n = 0$ | $\text{Beta}(1, 1)$ | $0.50$ |
| $n = 1$ | $\text{Beta}(2, 1)$ | $0.67$ |
| $n = 3$ | $\text{Beta}(4, 1)$ | $0.80$ |
| $n = 10$ | $\text{Beta}(11, 1)$ | $0.92$ |
This conservatism prevents overconfidence from lucky guesses while allowing genuine mastery to be recognized over time.
3. Component Attribution
Every human language is compositional: larger meanings are assembled from smaller parts. What makes Chinese especially convenient to model this way is that its writing system puts the structure right on the surface: words are made of visible characters, and characters of radicals and strokes. So when you successfully review a phrase like 好吃 (hǎochī, delicious), that tells us something not just about the phrase, but also about the individual characters 好 (hǎo) and 吃 (chī) that make it up. You clearly know those characters too; otherwise you couldn't have gotten the phrase right.
But here's the subtle part: not every character in a phrase deserves equal credit. If one character is much harder than another, and you got the whole phrase right, the hard character is the more impressive part. So we give it more credit.
Difficulty-Weighted Attribution
When a phrase's review is projected onto its components, each component's share of the credit is proportional to its difficulty. For component $i$ with difficulty $d_i$, the normalized weight is:
$$w_i = \frac{d_i}{\displaystyle\sum_{j} d_j}$$The sum runs over the phrase's full breadth-first decomposition (its constituent characters and, recursively, their radicals and strokes), so each individual share is small and the harder constituents receive the larger slices. Keeping the weighting proportional to difficulty (rather than something steeper) ensures that meaningful evidence reaches every component, not just the single hardest one. Every component receives the same outcome (an EASY parent yields an EASY implied signal for each); only the magnitude (the weight) differs.
For a review with outcome $O$, each component receives a weighted update:
$$\Delta\alpha_i = w_i \cdot \Delta\alpha(O) \qquad\qquad \Delta\beta_i = w_i \cdot \Delta\beta(O)$$Worked Example
Suppose you score EASY on 好吃 (hǎochī, delicious), where 好 (hǎo) has difficulty 6 and 吃 (chī) has difficulty 12. Looking just at the two characters:
$$w_{\text{好}} = \frac{6}{6 + 12} = \frac{6}{18} \approx 0.33 \qquad\qquad w_{\text{吃}} = \frac{12}{6 + 12} = \frac{12}{18} \approx 0.67$$So 吃 (chī) receives twice the attribution of 好 (hǎo): more credit for the harder character, while still sharing real evidence with the easier one. In the real system the decomposition continues all the way down to radicals and strokes, so each raw share is a fraction of a percent; the next section explains how those tiny shares are turned into useful evidence without ever being mistaken for proof.
4. Direct vs. Implied Evidence
Component attribution raises a delicate question. When you review 好吃 EASY, you've directly demonstrated that you know the phrase. But you've only indirectly demonstrated that you know 好 and 吃. Maybe you recognized the whole word as a unit, maybe context carried you. This implied evidence is real and useful, but it should never be mistaken for a direct test of those characters. Seeing a character in a hundred sentences should raise our belief that you know it, but not our confidence enough to declare it mastered and stop showing it to you.
The solution is to keep two separate ledgers behind every term, and to recombine them every time anything changes.
Two Buckets
- The direct bucket $(\alpha_d, \beta_d)$ is a Beta distribution fed only by full-weight direct reviews of the term itself, starting from a $\text{Beta}(0.5, 0.5)$ prior. These are the same $+1.0 / +0.7 / +0.3 / +1.0$ updates from §2.
- The implied bucket accumulates the small, difficulty-weighted shares from §3, added every time the term appears inside a phrase you review. Because each raw share is a fraction of a percent, it's lifted into a meaningful range by a $\text{gain}$ factor (≈ 40) before being added: $$w = \text{normalized\_weight} \times \text{gain} \qquad (\text{gain} \approx 40 \text{ lifts } \sim0.003 \to \sim0.12)$$
The Blend
The stored, effective distribution is recomputed from both buckets after every event. The implied bucket's pseudo-counts give an implied mean $m$, and its total mass, capped, gives an implied strength (precision):
$$m = \frac{a_i}{a_i + b_i} \qquad\qquad \text{strength} = \min(a_i + b_i,\; s_{\text{cap}}) \cdot \gamma(n_d)$$ $$\alpha_{\text{eff}} = \alpha_d + \text{strength} \cdot m \qquad\qquad \beta_{\text{eff}} = \beta_d + \text{strength} \cdot (1 - m)$$ $$\theta = \frac{\alpha_{\text{eff}}}{\alpha_{\text{eff}} + \beta_{\text{eff}}} \qquad\qquad \text{uncertainty} = \frac{1}{\alpha_{\text{eff}} + \beta_{\text{eff}} + 1}$$Two levers do all the work:
- The cap $\min(\cdot,\, s_{\text{cap}})$ limits how much precision context can buy. Context can drive $\theta$ high, but the uncertainty stays high, so a context-only term never passes a low-uncertainty mastery test. With $s_{\text{cap}} = 0.8$, a character seen only in EASY contexts lands at $\theta \approx 0.72$, $\text{uncertainty} \approx 0.36$: believed-known, just under the mastery line, and visibly unconfident.
- The subsumption factor $\gamma(n_d) = \dfrac{1}{1 + n_d / k}$ fades the implied bucket out as direct reviews arrive ($n_d$ is the direct-review count, $k = 3$). Implied influence halves after 3 direct reviews and quarters after 9. Once you've actually been tested enough, your score is purely direct-driven and the contextual evidence no longer matters.
Three States, Not Two
Separating belief from confidence creates a third state between "actively learning" and "retired": a term you probably know from context, but that the system hasn't yet confirmed:
| State | $\theta$ (belief) | Uncertainty | Driven by |
|---|---|---|---|
| Active | ~0.5 | high | being learned |
| Dormant (provisionally known) | high | still high | context only |
| Confirmed (retired) | high | low | direct reviews |
Context can move a term from Active → Dormant, so it stops adding cognitive load and stops being drilled, but only direct evidence can move it Dormant → Confirmed. Any "you've mastered this, stop reviewing" decision therefore requires both $\theta \geq 0.8$ and $\text{uncertainty} < 0.10$. The dormant state passes the first test and deliberately fails the second, so a provisionally-known term stays a low-frequency confirmation candidate, and is instantly resurfaced the moment a related item fails.
Forgetting Without a Nightly Sweep
Old contextual signals should fade: a term you saw hundreds of times last month shouldn't have its mean pinned by ancient encounters. Rather than re-touching every term every night (which would flood sync), decay is applied only on write, at the moment an event already touches the term:
$$\text{factor} = 0.5^{\,\Delta\text{days} \,/\, \text{halfLife}} \qquad (\text{halfLife} = 30 \text{ days})$$The implied counts are multiplied by this factor before the new signal is added. Between events the stored value is simply a frozen snapshot (exactly how the direct bucket already behaves), so reads stay a plain lookup. This keeps the implied mean responsive to recent performance.
Why This Matters: Transfer Learning
This two-bucket model delivers the system's core advantage (characters gain proficiency from the phrases that contain them) while keeping that inferred knowledge honest:
- Bootstrapping: before 好 is ever drilled on its own, EASY reviews of 好吃, 好看, 好听 accrue implied evidence and lift 好 into the dormant state: believed-known, not yet confirmed.
- Less redundant drilling: dormant components drop out of active rotation, so your reviews are spent on what's genuinely new rather than re-proving what you already know.
- Self-correcting: because dormant terms keep high uncertainty, a single related failure resurfaces them immediately; context is treated as a hypothesis, not a verdict.
| Parameter | Default | Role |
|---|---|---|
| $\text{gain}$ | $40$ | Lifts a contextual encounter from negligible (~0.003) to meaningful (~0.12 "EASY-units") |
| $s_{\text{cap}}$ | $0.8$ | Caps implied precision: bounds confidence, not belief |
| $k$ | $3$ | Subsumption rate: implied influence halves at $k$ direct reviews |
| $\text{halfLife}$ | $30$ days | Decay-on-write half-life for stale contextual signals |
5. Skill Level Tracking (Kalman-Style IRT)
Beyond tracking individual cards, the app also maintains an estimate of your overall skill level: a single number that represents roughly how difficult of a card you can handle. This number lives on the same difficulty scale as the dictionary: a skill level of 100 means you should be comfortable with characters and phrases up to difficulty 100.
This estimate is deliberately cautious: it only moves when you do something surprising. Getting an easy card right doesn't prove anything new, and getting a hard card wrong is expected. But getting a hard card right? That's meaningful evidence that your skill level should increase.
Technically, this uses a Kalman-filter-style update layered on Item Response Theory. Each informative review is treated as a noisy Gaussian observation of your true skill, and the update size scales with how much information that observation actually carries. A clean observation near your current estimate moves the needle; a wild outlier gets appropriately discounted.
Two-Parameter Model
Your skill is represented by $\mu$ (current estimate) and $\sigma$ (uncertainty):
$$\text{skill} = \big(\mu,\; \sigma\big) \qquad \text{initially} \;(\mu_0 = 0,\;\sigma_0 = 75)$$The 95% confidence interval at any time is:
$$\text{CI}_{95\%} = \mu \pm 1.96\,\sigma$$Asymmetric Evidence Clamping
The first filter on the update is the informativeness clamp: not all review outcomes are equally diagnostic. Imagine you're a skill-100 learner who gets a difficulty-5 card right; of course you did, that's trivial for you. But if you get a difficulty-150 card right, that's real news. The system only moves your skill estimate when the outcome is genuinely surprising:
| Outcome | Card vs $\mu$ | Informative? | Effect |
|---|---|---|---|
| Success | difficulty $> \mu$ | Yes | $\mu$ moves toward difficulty |
| Failure | difficulty $< \mu$ | Yes | $\mu$ moves toward difficulty |
| Success | difficulty $\leq \mu$ | No | No change to $\mu$ |
| Failure | difficulty $\geq \mu$ | No | No change to $\mu$ |
The Kalman Update Rule
When a surprising outcome fires, we treat the card difficulty $d$ as a noisy measurement of your true skill. The observation variance depends on how far the card is from your current estimate, and on how strong the outcome signal is:
$$\tau^2 \;=\; \tau_0^{\,2} \cdot \left(1 + \left(\frac{d - \mu}{\text{distanceScale}}\right)^{\!2}\right) \;\big/\; \text{strength}(\text{outcome})$$Then the Kalman gain $K$ determines how much of the gap we close:
$$K \;=\; \frac{\sigma^2}{\sigma^2 + \tau^2} \qquad\qquad \mu' \;=\; \mu + K \cdot (d - \mu) \qquad\qquad \sigma'^{\,2} \;=\; (1 - K)\,\sigma^2$$Three properties fall out of this elegantly:
- Distant outliers barely move $\mu$. A card far from your current estimate produces a large $\tau^2$, which shrinks $K$, which shrinks the update. A single success on a $d=500$ card when $\mu=50$ moves you from 50 to about 73, not 207.
- Occasional holes don't drag down a confident estimate. Once $\sigma$ is small, the same distant observation produces an even smaller $K$. A single FAIL on an easy $d=50$ card when $\mu=200$ barely nudges you.
- The $\mu$ update is convex. Because $K \in [0,1]$, $\mu'$ always lands between your current estimate and the card difficulty; it can never overshoot the difficulty of the card you just answered.
Outcome Strengths
The strength weight reflects how much signal each outcome carries. EASY and FAIL are clean "I know it" or "I don't know it" signals; GOOD is slightly noisier; HARD is noisiest of all because it mixes partial success with partial failure:
| Outcome | strength | Interpretation |
|---|---|---|
| EASY | $1.0$ | Clean success signal |
| GOOD | $0.7$ | Success with a little friction |
| HARD | $0.4$ | Ambiguous: mixes partial success and failure |
| FAIL | $1.0$ | Clean failure signal |
Uncertainty That Breathes
A plain Kalman filter only ever shrinks its uncertainty: because $\sigma'^2 = (1-K)\sigma^2$, every update makes $\sigma$ smaller and never larger. That's exactly right when you're measuring a quantity that holds still, but a learner's skill isn't fixed; it grows, sometimes in jumps. If $\sigma$ is allowed to collapse, the gain $K$ collapses with it and the estimate stops tracking a learner who is still changing.
The fix is to let $\sigma$ breathe: widening when the model is genuinely surprised, tightening when it's well-calibrated. Before each Kalman correction, we inflate the prior variance in proportion to how badly recent predictions have missed. The key quantity is the normalized innovation: how big the prediction error is relative to how much error the model expected.
We score each outcome on a 0–1 scale ($s$: FAIL 0, HARD 0.3, GOOD 0.85, EASY 1.0) and compare it to the score the model predicted, $p_e$, with predicted variance $p_v$ (both from a four-outcome graded-response model evaluated at the card's difficulty):
$$\text{NIS} = \min\!\left(\frac{(s - p_e)^2}{p_v + \text{floor}},\; \text{nisCap}\right) \qquad\qquad \overline{\text{NIS}}' = (1-\lambda)\,\overline{\text{NIS}} + \lambda\,\text{NIS}$$ $$\text{inflation} = 1 + q \cdot \max\!\big(0,\; \overline{\text{NIS}}' - 1\big) \qquad\qquad \text{priorVar} = \sigma^2 \cdot \text{inflation}$$A perfectly calibrated model has $\overline{\text{NIS}} = 1$: its residuals are exactly as big as it expected. Only when a smoothed NIS runs persistently above 1 (meaning $\mu$ is genuinely mis-estimated, not just that you drew an inherently 50/50 card) does the inflation factor exceed 1 and re-open the prior. This inflated $\text{priorVar}$ then takes the place of $\sigma^2$ in the Kalman update above. The knob $q$ controls how strongly surprise re-opens the prior; at $q = 0$ inflation switches off and $\sigma$ only shrinks.
The noise floor. The denominator includes a floor that grows with difficulty, $\text{floor} = 0.08 + 0.30 \cdot (d / 1000)$, fit to real review data. It absorbs the "honest" unpredictability the difficulty score can't capture: non-compositional idioms whose easy components hide a hard meaning (上门, 心里过不去) and technical or proper terms (奥尔特云, "Oort cloud"). This keeps genuine model noise from being mistaken for a mis-estimated skill.
Convergence Example
A new user reviewing an ascending curriculum (difficulties climbing from 20 to 500, true skill = 400) over 300 reviews:
| Review | Difficulty | $\mu$ |
|---|---|---|
| 1 | $20$ | $14$ |
| 30 | $66$ | $49$ |
| 60 | $114$ | $87$ |
| 120 | $210$ | $186$ |
| 180 | $306$ | $283$ |
| 240 | $402$ | $367$ |
| 300 | $498$ | $395$ |
The estimate tracks true skill to within about 1%: smoothly, with no wild swings. With breathing uncertainty, $\sigma$ stays elevated through the steep early climb (while $\mu$ is still moving) and only tightens once $\mu$ nears the true skill: an honest confidence interval throughout.
The payoff. Against a synthetic learner whose true skill is already ~650 on day one, breathing uncertainty calibrates $\mu$ to the true level in just 25–40 reviews, and because the $\mu$ update is convex, it does so without ever overshooting.
Uncertainty Floors and Ceilings
Uncertainty is now a living quantity, pushed down by informative reviews near $\mu$ and back up by surprising prediction errors. The balance of those two forces sets a dynamic steady state rather than pinning $\sigma$ at a floor. A floor of $\sigma_{\min} = 10$ guards only against pathological overconfidence (low enough to let a genuinely well-calibrated estimate report a tight interval), and a ceiling of $\sigma_{\max} = 120$ bounds how far surprise can re-open the prior, keeping the math well-conditioned even under model mismatch.
6. Cognitive Load Model
Have you ever crammed too many new vocabulary words at once and ended up remembering none of them? That's cognitive overload, and it's the biggest enemy of effective language learning. duiduidui! constantly monitors how much mental effort you're juggling and adjusts the pace accordingly. When you're fresh and things are clicking, it introduces new material. When you're at capacity, it slows down and focuses on reinforcing what you've already seen.
The system tracks four factors to estimate your cognitive load: how uncertain each card is, how well you know it, how hard it is relative to your level, and how much total capacity you have as a learner.
Only active cards (cards you've directly reviewed) contribute to cognitive load. Cards in the dormant state, known only through implied evidence from component attribution (§4), are excluded. They represent inferred knowledge, not active learning burden, which is exactly why the implied bucket is careful never to let context alone retire a card: a dormant term has already stopped costing you cognitive load.
Attention Demand
A card you've never seen demands maximum attention; it could be anything. A card you've reviewed many times, whether you know it well or not, demands less attention because you've at least categorized it in your mind. The system measures this using the statistical uncertainty of your knowledge estimate:
$$\text{Var}[\theta] = \frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}$$We normalize this into an attention score:
$$\text{attention} = \sqrt{\frac{\text{Var}[\theta]}{0.25}}$$Maximum attention occurs at $\text{Beta}(1,1)$ (never seen). As evidence accumulates, attention drops toward zero regardless of whether the card is easy or hard; you've simply categorized it.
Mastery Factor
As you get better at a card, it takes up less mental space. This isn't a sudden switch. It's a smooth curve where every small improvement in proficiency gives you a little more breathing room. A card you're 80% confident about barely registers as cognitive load at all:
$$m(\theta) = \frac{1}{1 + e^{12(\theta - 0.65)}}$$| $\theta$ | $m(\theta)$ | Relief |
|---|---|---|
| $0.50$ | $0.95$ | 5%: card is starting to stick |
| $0.60$ | $0.73$ | 27%: knowledge solidifying |
| $0.70$ | $0.38$ | 62%: mostly mastered |
| $0.80$ | $0.11$ | 89%: fully mastered |
| $0.90$ | $0.01$ | 99%: automatic recall |
Every increment in proficiency provides tangible relief: no plateau feeling.
Relative Difficulty Weight
Difficulty is always relative. A card with difficulty 50 is overwhelming for a beginner but trivial for an intermediate learner. The system compares each card's difficulty to your current skill level, so a card that matches your level always contributes the same amount of load, whether you're on day one or day three hundred:
$$r = \sqrt{\frac{\text{difficulty}}{\text{effective\_skill}}} \qquad\text{where}\quad \text{effective\_skill} = \max(\mu, 10)$$A card at your skill level always has weight $\approx 1.0$, whether you're a beginner (skill 10, difficulty 10) or intermediate (skill 500, difficulty 500).
Cognitive Capacity
Beginners can only juggle a handful of cards before feeling overwhelmed. But as you learn more Chinese, you develop mental shortcuts, pattern recognition, and contextual anchors that let you handle more material at once. The system models this growing capacity: an advanced learner can comfortably manage ten times more active cards than a beginner:
$$\text{capacity} = \text{effective\_skill}^{\,0.6}$$| Skill Level | Capacity | ~Cards for 80% Load |
|---|---|---|
| 10 (beginner) | $4.0$ | ~10 |
| 100 | $15.9$ | ~39 |
| 500 (intermediate) | $41.6$ | ~100 |
| 2000 (advanced) | $95.6$ | ~235 |
Combined Load
Putting it all together: each card's cognitive cost depends on how uncertain it is, how well you know it, how hard it is for someone at your level, and your total capacity. Sum these up across all your active cards and you get a single number that represents how "full" your brain is:
$$\ell_i = \frac{\text{attention}_i \;\cdot\; m(\theta_i) \;\cdot\; r_i}{\text{capacity}}$$Total cognitive load is the sum across all active cards:
$$L = \sum_{i \in \text{active}} \ell_i$$The capacity normalization ensures that "80% loaded" always means "near your personal limit," whether that limit is 10 cards or 235.
7. Adaptive Card Selection
This is where all the pieces come together to answer the most important question: what should you study next? The system faces a fundamental tradeoff: introduce something new and exciting, or go back and reinforce something you're still learning? Too much new material and you'll forget everything. Too much review and you'll get bored. The answer is to route every choice through a small set of specialized pools.
The Five-Pool Router
Rather than a single new-vs-review coin flip, the selector draws from five pools, each with a distinct pedagogical job:
- Reinforcement: cards you're actively learning, surfaced when their proficiency sits in the sweet spot (around $\theta = 0.5$) where another look pays off most.
- Composition (building up): when you know the components, the system offers the compound they form (好 + 人 → 好人), turning settled knowledge into new reach.
- Decomposition (breaking down): when a compound keeps tripping you up, the system drills the individual pieces underneath it.
- Discovery: genuinely new material at the frontier of your ability, introduced at a carefully chosen difficulty (see below).
- Stale: things you haven't seen in a long time, resurfaced before forgetting sets in.
How much each pool contributes depends on your live cognitive load (§6): when you have headroom, the new-material pools open up; as you approach capacity, the router shifts toward Reinforcement and Stale.
Difficulty Targeting: A Success-Rate Controller
How hard should new material be? The intuitive answer, "your skill estimate $\mu$, plus a little," turns out to be subtly dangerous, because the estimate can be confidently wrong. If you're served easy cards, the model is never surprised, so $\sigma$ collapses and $\mu$ sits there under-calibrated, and a $\mu$-based target would happily park you on easy cards forever, certain it was doing the right thing.
So new-material difficulty isn't read off $\mu$ at all. It comes from a separate probe difficulty $P$, nudged after each frontier review by a simple controller that chases a target success rate:
$$P \;\leftarrow\; \max\!\big(0,\; P + \eta \cdot (\text{score} - \text{target})\big) \qquad (\text{only when } d \geq P - \text{band})$$where $\text{score}$ is the outcome on the 0–1 scale (FAIL 0, HARD 0.3, GOOD 0.85, EASY 1.0), $\text{target}$ is the success rate you're aiming for, and $\eta \approx 70$ difficulty units is the step size. When you ace frontier cards, $P$ climbs; when you start failing them, $P$ falls, so it converges on the difficulty where your observed success rate equals the target, from either direction. The frontier guard ($d \geq P - \text{band}$, with $\text{band} \approx 80$) ensures only genuinely new material moves $P$; acing an easy reinforcement card is ignored.
Because the controller keys off observed success (ground truth) rather than the model's prediction, it can't be fooled by a confidently wrong estimate: keep acing, and $P$ keeps climbing no matter how sure $\mu$ and $\sigma$ are. This cleanly separates the two jobs. The "challenge level" you pick in settings simply chooses the target success rate (an easier setting sits you at a higher success rate); $\mu$ and $\sigma$ are freed to do what they're good at (driving the display, the confidence bands, cognitive load, and the breathing-uncertainty math of §5) without also having to pick your next card.
8. How It All Fits Together
Every time you tap a button (Easy, Good, Hard, or Fail), you set off a chain reaction through every layer of the system. Your knowledge estimates update, your skill level adjusts, your cognitive load recalibrates, and the next card is chosen. It all happens in milliseconds, and the result is a learning experience that feels like it's reading your mind.
Here's the full cascade:
- Review event: Update the phrase's direct Bayesian bucket, and propagate small, linearly-weighted implied updates down through every component: characters, radicals, and strokes.
- Effective scores: Re-blend each affected term's direct and implied buckets, capping how much confidence context can buy, so newly-known components settle into the dormant state.
- Skill update: Inflate the uncertainty by how surprising the outcome was, then, if the outcome is informative, apply the Kalman correction, moving $\mu$ and reshaping $\sigma$ in proportion to the real evidence.
- Cognitive load: Recalculate $L$ from all active cards' attention, mastery, relative difficulty, and capacity.
- Card selection: Weight the five pools by cognitive load, and choose the next card, drawing new material at the difficulty the success-rate controller has converged on.
What makes this system robust is that it self-corrects. Several natural feedback loops keep everything in balance:
- Overload protection: High $L$ → fewer new cards → $L$ decreases.
- Progress acceleration: Low $L$ → more new cards → sustained challenge.
- Difficulty calibration: The success-rate controller chases your real performance → new material lands at the right challenge regardless of estimate error.
- Confidence that breathes: Informative reviews shrink $\sigma$; surprising prediction errors re-widen it → the reported confidence stays honest as you change.
- Transfer learning: Phrase reviews build implied evidence about components → fewer redundant drills, with high uncertainty as a safety net.
The result is a learning experience that feels effortless, because the mathematics are doing the heavy lifting behind every card.