<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Chen</title>
    <description>The latest articles on DEV Community by Alex Chen (@alexrchen).</description>
    <link>https://dev.to/alexrchen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3994911%2F704b5100-cf3a-4408-92c7-7d8476aceda6.jpg</url>
      <title>DEV Community: Alex Chen</title>
      <link>https://dev.to/alexrchen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexrchen"/>
    <language>en</language>
    <item>
      <title>Why LLM-Generated Flashcards Are Usually Bad, and How to Screen Them</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Sat, 25 Jul 2026 08:11:06 +0000</pubDate>
      <link>https://dev.to/alexrchen/why-llm-generated-flashcards-are-usually-bad-and-how-to-screen-them-20mi</link>
      <guid>https://dev.to/alexrchen/why-llm-generated-flashcards-are-usually-bad-and-how-to-screen-them-20mi</guid>
      <description>&lt;p&gt;I've watched a lot of teams bolt an LLM onto a document and call the output flashcards. It demos beautifully and it teaches badly. The generated cards look plausible — grammatical, on-topic, correctly formatted — and they fall apart the moment a real learner tries to review them six weeks later.&lt;/p&gt;

&lt;p&gt;The core problem is that "summarize this into Q&amp;amp;A pairs" optimizes for a different objective than "produce items that are cheap to recall and hard to fake." Those two things diverge fast. Here's what I've learned building generation pipelines that survive contact with a review queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Failure Modes Are Predictable
&lt;/h2&gt;

&lt;p&gt;Naive generation produces four bad card shapes, over and over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compound questions.&lt;/strong&gt; "What is the TCP three-way handshake and why does it exist?" That's two cards wearing a trench coat. The learner half-recalls, grades it "Good," and the scheduler now believes both facts are known. You've corrupted the scheduling signal, which is worse than not having the card at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ambiguous prompts.&lt;/strong&gt; "What year did it happen?" — extracted from a paragraph where "it" was obvious. On the card, out of context, there's no unique answer. The learner fails, marks it Again, and it returns tomorrow to fail identically. This is the single most common defect I see, because the model has the source context in its window and doesn't notice that the card doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloze deletions with too little residual signal.&lt;/strong&gt; Take "The mitochondrion is the powerhouse of the cell." Blank the subject and you get a reasonable card. Blank two spans and the second one is often recoverable from grammar alone. A cloze that can be solved by syntax trains nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pattern-matchable answers.&lt;/strong&gt; If every card in a German-nouns deck has an answer starting with "der," the learner learns the deck, not the material. This only shows up at deck level, so per-card evaluation never catches it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Atomicity Is a Structural Constraint, Not a Prompt Instruction
&lt;/h2&gt;

&lt;p&gt;Telling the model "make cards atomic" gets you partial compliance and no guarantees. The reliable move is to make the pipeline structurally incapable of emitting a compound card: split extraction from card-writing.&lt;/p&gt;

&lt;p&gt;Stage one extracts &lt;em&gt;claims&lt;/em&gt; — self-contained propositions with entities already resolved. Stage two turns one claim into one card. A card can't be compound if its input was a single claim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CLAIM_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# resolved entity, no pronouns
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;predicate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;# exactly one assertion
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;qualifiers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# conditions / scope
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_span&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# provenance, for verification
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_claims&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# one call, constrained decoding into the schema
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;claim_to_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Claim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# template chosen deterministically from claim shape;
&lt;/span&gt;    &lt;span class="c1"&gt;# the model only writes the natural-language surface
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second win is that pronoun resolution happens at claim time, with the source paragraph still in view. By the time you're writing the card, the subject is already a proper noun, and the "what year did it happen" class of bug becomes unrepresentable rather than merely discouraged.&lt;/p&gt;

&lt;p&gt;For cloze specifically, I stopped letting the model choose the deletion span freely. Instead: extract the claim, identify which token carries the claim's information payload (usually the object of the predicate, or a numeric qualifier), and delete that. If a sentence has no high-information span, it doesn't become a cloze — it becomes a Q&amp;amp;A card, or nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screening Card Quality Without Humans
&lt;/h2&gt;

&lt;p&gt;You can catch most defects with cheap deterministic checks before anything reaches a learner. These are the ones that earned their keep:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# compound: coordination joining two verb phrases in the prompt
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(and|as well as)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;has_two_verb_phrases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;compound&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# unresolved reference
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(it|this|they|these|the former|above)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ambiguous_reference&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# answer leakage: content words shared between prompt and answer
&lt;/span&gt;    &lt;span class="n"&gt;back_words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;content_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;content_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;back_words&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;back_words&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;back_words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer_in_prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# trivial cloze: deleted span is a function word
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cloze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;is_closed_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_span&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trivial_cloze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# verbosity: long answers are usually compound in disguise
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer_too_long&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then two model-based checks worth the tokens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answerability without source.&lt;/strong&gt; Give a fresh model only the card front — no document — and ask it to answer. If it can't produce the expected answer, the prompt is under-specified. If it produces the answer trivially for material the learner shouldn't already know, the prompt is leaking. Batch this per generation job; it's one call for many cards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deck-level distinctness.&lt;/strong&gt; Embed every front and flag pairs above a cosine-similarity threshold. Near-duplicate fronts with different backs are the nastiest failure in spaced repetition: the learner can't tell which card they're looking at and starts guessing from queue position. Anything very close in a decent sentence embedding gets merged or rewritten.&lt;/p&gt;

&lt;p&gt;The screening layer rejects a meaningful share of raw generations, and the rejection &lt;em&gt;rate&lt;/em&gt; turns out to be the most useful signal we have about source material. A chapter with a high reject rate is usually narrative prose with few extractable claims — which means the fix is the chunking strategy, not the card prompt. That whole extract-screen-regenerate loop is what sits behind card generation in &lt;a href="https://smartrecallai.com" rel="noopener noreferrer"&gt;SmartRecall&lt;/a&gt;, and it's the part that took longest to get right; the initial "prompt an LLM for flashcards" version took an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Skip Next Time
&lt;/h2&gt;

&lt;p&gt;I spent too long on a learned quality classifier. The deterministic screens plus one answerability check catch nearly everything it would, and they're debuggable — when a card is rejected I can point at the rule that rejected it.&lt;/p&gt;

&lt;p&gt;I'd also ship with cloze generation off by default. Q&amp;amp;A cards fail loudly: the learner can't answer. Bad clozes fail quietly: the learner answers from syntax and feels productive. Ship the loud failure mode first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>programming</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>SM-2 Is Not Enough: Where Classic Spaced Repetition Breaks Down</title>
      <dc:creator>Alex Chen</dc:creator>
      <pubDate>Fri, 24 Jul 2026 19:21:26 +0000</pubDate>
      <link>https://dev.to/alexrchen/sm-2-is-not-enough-where-classic-spaced-repetition-breaks-down-4hdd</link>
      <guid>https://dev.to/alexrchen/sm-2-is-not-enough-where-classic-spaced-repetition-breaks-down-4hdd</guid>
      <description>&lt;p&gt;SM-2 — the algorithm behind Anki and most spaced repetition tools — was published in 1987 and is remarkably durable. It is also wrong in specific, predictable ways that matter once your deck grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SM-2 does
&lt;/h2&gt;

&lt;p&gt;Each card carries an ease factor, starting at 2.5. Grade a review 0-5; on success the interval multiplies by ease, on failure it resets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reps&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reps&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reps&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;reps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;ease&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.08&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple, no training data, works offline. Genuinely good engineering for 1987.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 1: ease hell
&lt;/h2&gt;

&lt;p&gt;Every lapse decrements ease and it recovers slowly. A card you failed a few times early gets pinned near the 1.3 floor — permanently reviewed at short intervals even after you have learned it perfectly.&lt;/p&gt;

&lt;p&gt;The algorithm has no way to say "this card was hard &lt;em&gt;then&lt;/em&gt;, it is easy &lt;em&gt;now&lt;/em&gt;." Ease is a one-way ratchet in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 2: binary-ish grading carries too much weight
&lt;/h2&gt;

&lt;p&gt;The 0-5 scale collapses in practice to "got it / did not." But &lt;em&gt;how&lt;/em&gt; you recalled it matters — instant recall and dredging it up after eight seconds are very different memory states that produce identical grades.&lt;/p&gt;

&lt;p&gt;Response latency is a strong signal and SM-2 ignores it entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 3: cards are treated as independent
&lt;/h2&gt;

&lt;p&gt;They are not. Learning "kanji A" helps with "compound containing A." Confusable pairs interfere with each other. SM-2 schedules every card in isolation, so interference-prone cards get scheduled adjacently by chance, which is the worst possible arrangement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 4: no forgetting curve per user
&lt;/h2&gt;

&lt;p&gt;SM-2 assumes one exponential decay shape. Real retention varies by person, by material type, and by time of day. A fixed curve over-schedules some users and under-schedules others, and neither group can tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  What modern approaches change
&lt;/h2&gt;

&lt;p&gt;FSRS models memory with three variables — difficulty, stability, retrievability — and fits parameters to &lt;em&gt;your&lt;/em&gt; review history. Concretely that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Difficulty is not a one-way ratchet&lt;/li&gt;
&lt;li&gt;You can target an explicit retention rate (say 0.9) rather than accepting whatever falls out&lt;/li&gt;
&lt;li&gt;Latency and grade both inform the update&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is that it needs review history to fit, so cold-start is worse. Reasonable compromise: SM-2 defaults until you have a few hundred reviews, then refit.&lt;/p&gt;

&lt;p&gt;I work on this at &lt;a href="https://smartrecallai.com" rel="noopener noreferrer"&gt;SmartRecall&lt;/a&gt; — AI-generated cards with scheduling that does not pin everything to the ease floor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveat
&lt;/h2&gt;

&lt;p&gt;For small decks with few lapses, SM-2 is fine and the added complexity buys nothing. These failures compound with deck size and time — they are invisible at 200 cards and painful at 5000.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>python</category>
      <category>learning</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
