<?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: tem chelsy</title>
    <description>The latest articles on DEV Community by tem chelsy (@chelsy).</description>
    <link>https://dev.to/chelsy</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3782746%2F9655e6e5-dc92-4074-881a-705d31d0e64c.jpg</url>
      <title>DEV Community: tem chelsy</title>
      <link>https://dev.to/chelsy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chelsy"/>
    <language>en</language>
    <item>
      <title>My First System Design Class Taught Me More Than a Year of Coding</title>
      <dc:creator>tem chelsy</dc:creator>
      <pubDate>Mon, 11 May 2026 10:53:30 +0000</pubDate>
      <link>https://dev.to/chelsy/my-first-system-design-class-taught-me-more-than-a-year-of-coding-38n8</link>
      <guid>https://dev.to/chelsy/my-first-system-design-class-taught-me-more-than-a-year-of-coding-38n8</guid>
      <description>&lt;p&gt;I walked into my first system design class expecting to write code. I walked out drawing boxes on a whiteboard and asking questions I had never thought to ask before. No syntax. No frameworks. Just shapes, arrows, and a deceptively simple prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Design a service where users paste in a long URL and get back a short one.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was it. The whole brief. Something every developer has used a hundred times — bit.ly, TinyURL, the link shortener baked into Twitter. I had never once thought about what was behind it. By the end of that session, I couldn't stop thinking about it.&lt;/p&gt;

&lt;p&gt;This is what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The system design interview has a shape
&lt;/h2&gt;

&lt;p&gt;Before we drew anything, our instructor made one thing clear: every system design problem follows the same five steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clarify → Estimate → Sketch → Deep dive → Failure modes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That structure sounds obvious on paper. In practice, it is the hardest discipline to maintain. Every instinct in a developer's brain says &lt;em&gt;start building&lt;/em&gt;. System design punishes that instinct. The first thing you do is ask questions — not answer them.&lt;/p&gt;

&lt;p&gt;For the URL shortener, those questions were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this read-heavy or write-heavy?&lt;/li&gt;
&lt;li&gt;How short is "short"? What is the key format?&lt;/li&gt;
&lt;li&gt;Do URLs expire?&lt;/li&gt;
&lt;li&gt;Do users have accounts, or is it anonymous?&lt;/li&gt;
&lt;li&gt;Do we need analytics?&lt;/li&gt;
&lt;li&gt;Is this a global service?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each answer reshapes the entire design. If URLs never expire, you don't need TTL logic anywhere in the system. If it's anonymous only, you don't need a users table. If you don't need analytics, you can use a 301 redirect instead of a 302 — a small technical detail with surprisingly large consequences.&lt;/p&gt;

&lt;p&gt;The act of clarifying is not stalling. &lt;strong&gt;It is the design.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  One number that changes everything
&lt;/h2&gt;

&lt;p&gt;Once we agreed on scope, we did math. Quick, rough, back-of-envelope math — the kind where you round aggressively and the goal is order of magnitude, not precision.&lt;/p&gt;

&lt;p&gt;Assume 100 million URLs created per day. That is roughly &lt;strong&gt;1,200 writes per second&lt;/strong&gt;. Now assume each short URL gets clicked ten times on average. That is &lt;strong&gt;12,000 reads per second&lt;/strong&gt;. Round up for peak traffic and you are looking at something closer to &lt;strong&gt;120,000 reads per second&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The ratio of reads to writes is about &lt;strong&gt;100 to 1&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Key idea:&lt;/strong&gt; That single number — 100:1 — is the most important fact about a URL shortener. It tells you where to spend your engineering effort, what will break first, and what your architecture needs to optimise for.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This system is not a writing system that also does reads. It is a &lt;strong&gt;reading system that occasionally accepts writes&lt;/strong&gt;. That distinction changes every decision that follows.&lt;/p&gt;




&lt;h2&gt;
  
  
  The two endpoints
&lt;/h2&gt;

&lt;p&gt;The API for a URL shortener is almost insultingly simple. Two endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /shorten    →  takes a long URL, returns a short code
GET  /:code      →  redirects to the original URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second endpoint is where all the complexity lives. It runs 100 times more often than the first. It needs to respond in under 10 milliseconds. It needs to handle viral traffic spikes — a single link being clicked by millions of people in minutes.&lt;/p&gt;

&lt;p&gt;There is also a small but meaningful technical decision hiding in that GET endpoint: &lt;strong&gt;301 or 302?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;301 Permanent&lt;/th&gt;
&lt;th&gt;302 Temporary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browser caches it?&lt;/td&gt;
&lt;td&gt;✅ Yes — forever&lt;/td&gt;
&lt;td&gt;❌ No — re-checks every time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics work?&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can expire the URL?&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Saves bandwidth?&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;We use 302.&lt;/strong&gt; The analytics and expiry control are worth the extra traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  How do you generate the short code?
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. There are three approaches, each with real trade-offs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1 — Hash the URL
&lt;/h3&gt;

&lt;p&gt;Run the long URL through MD5. Take the first 43 bits. Encode in base62. You get a 7-character code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Same URL always gives the same short code. Natural deduplication.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Con:&lt;/strong&gt; Collisions happen at scale. You need a DB check and retry loop on every write.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 2 — Auto-incrementing counter
&lt;/h3&gt;

&lt;p&gt;Keep a global counter. Every new URL gets the next number, encoded in base62.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Simple, no collisions.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Con:&lt;/strong&gt; A global counter is hard to share across many servers. At 1,200 writes/sec it becomes a bottleneck. Also leaks how many URLs have been created.&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 3 — Key Generation Service ✅ (preferred)
&lt;/h3&gt;

&lt;p&gt;A separate service pre-generates a pool of random 7-character base62 keys offline. The Write API claims one atomically. Each server holds a local batch of 1,000 keys in memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Zero collisions. No retry loops. Sub-millisecond key assignment.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Con:&lt;/strong&gt; Extra service to maintain. Needs at least two replicas (it is a single point of failure).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Remember:&lt;/strong&gt; In a design interview, none of these is wrong. What matters is naming the trade-offs and defending your choice.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Where does the data live?
&lt;/h2&gt;

&lt;p&gt;The storage layer has two parts working together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; holds the source of truth — a &lt;code&gt;urls&lt;/code&gt; table mapping each short key to its long URL, owner, creation time, and expiry. We use read replicas (three or four of them) because the redirect path is read-only and those 120,000 reads per second can be distributed across multiple servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; sits in front of Postgres as a cache. Every time a URL is created, it is written to both Postgres and Redis simultaneously (write-through caching). When someone follows a short link, the Read API checks Redis first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache hit  → return long URL instantly (no DB touched)
Cache miss → fetch from Postgres, repopulate Redis, return URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At an 80% cache hit rate, only &lt;strong&gt;24,000 of those 120,000 reads per second&lt;/strong&gt; reach the database. Without caching, you would need five times more database capacity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Caching is the cheat code for read-heavy systems.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The full architecture
&lt;/h2&gt;

&lt;p&gt;When you put all of this together, the system looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         Client / Browser
               ↓
         Load Balancer
         ↙            ↘
    Write API        Read API
    ↙      ↘              ↓
Key Gen  PostgreSQL ← Redis
              ↓         (cache miss)
         Analytics
          (async)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The write path and read path are &lt;strong&gt;intentionally separated&lt;/strong&gt;. Writes are rare and can be slightly slower — a user waiting 200ms to get a short link is acceptable. Reads are constant and must be instant.&lt;/p&gt;

&lt;h3&gt;
  
  
  How analytics work without slowing redirects
&lt;/h3&gt;

&lt;p&gt;This is one of the trickiest parts. A redirect must be fast. Recording a click is slow. You cannot do both synchronously.&lt;/p&gt;

&lt;p&gt;When someone follows a short link, the Read API returns the 302 redirect &lt;strong&gt;immediately&lt;/strong&gt;. After the response is sent — while the user is already being redirected — the server records the click to an in-memory queue. A background process drains that queue every 2 seconds with a single bulk &lt;code&gt;INSERT&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks → 302 returned instantly
            ↓ (after response, async)
        click pushed to queue
            ↓ (every 2 seconds)
        bulk INSERT into clicks table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user never waits. The database is not hammered. This is the core principle: &lt;strong&gt;never make the user wait for work they don't need to see the result of.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What could go wrong
&lt;/h2&gt;

&lt;p&gt;This is the senior-engineer move. After the happy path works, ask: &lt;em&gt;what fails?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔴 Cache stampede
&lt;/h3&gt;

&lt;p&gt;A viral link goes cold, its cache entry expires, then suddenly 100,000 users click it at the same moment. All miss Redis and flood Postgres simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Redis mutex lock on miss — one thread fetches from DB, the rest wait briefly for the cache to repopulate.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔴 Key Generation Service goes down
&lt;/h3&gt;

&lt;p&gt;No new short URLs can be created. The entire write path is blocked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Run 2+ KGS replicas. Each Write API server holds a local batch of 1,000 pre-fetched keys and continues working for minutes during a KGS outage.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔴 Single link gets too hot
&lt;/h3&gt;

&lt;p&gt;5 million clicks per minute overwhelms even the Redis shard serving that key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Local in-memory cache on each Read API server (5s TTL). CDN caches the redirect at the edge — requests never reach your servers at all for the hottest links.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟡 Database primary fails
&lt;/h3&gt;

&lt;p&gt;Writes are blocked. Read replicas continue but may be slightly stale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Automated leader election (Patroni). Queue writes in Redis during the ~30-second failover window.&lt;/p&gt;

&lt;h3&gt;
  
  
  🟡 Expired URL still in Redis
&lt;/h3&gt;

&lt;p&gt;The URL expired in Postgres but Redis still returns the old entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Set Redis TTL 30 seconds shorter than the actual expiry. Check &lt;code&gt;expires_at&lt;/code&gt; on every DB read as a safety net.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually learned
&lt;/h2&gt;

&lt;p&gt;The URL shortener is the "hello world" of system design for a reason. It is small enough to finish in one session and rich enough to introduce almost every major concept: caching, replication, key generation, rate limiting, async processing, failure modes.&lt;/p&gt;

&lt;p&gt;But the deeper lesson was not about any of those things specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The shape of a system matters more than the code inside it.&lt;/strong&gt;&lt;br&gt;
The same code that works for 1,000 users collapses under 1,000,000 — not because the code changed, but because the shape around it was never designed to scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The first question is never "how do I build this?"&lt;/strong&gt;&lt;br&gt;
It is "what does this actually need to do?" The 100:1 ratio was not a number I calculated and forgot. It was a constraint that filtered every subsequent decision. Good system design is a chain of consequences from the question you asked at the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Naming what fails is not pessimism — it is engineering.&lt;/strong&gt;&lt;br&gt;
Every system fails eventually. The difference between a resilient system and a fragile one is not whether failures happen, but whether someone thought through what to do when they do.&lt;/p&gt;

&lt;p&gt;I came in expecting to write code. I left knowing how to think about systems.&lt;/p&gt;

&lt;p&gt;That is a harder skill. And a more important one.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is my write-up from Session 1 of a system design course. If you are following along, your homework is to sketch this same architecture on your own — without looking at notes — and find where you get stuck. That is exactly where the learning is.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drop your questions or your own diagrams in the comments. Let's figure it out together.&lt;/em&gt; 👇&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Implemented the SM-2 Spaced Repetition Algorithm in My Flashcard App</title>
      <dc:creator>tem chelsy</dc:creator>
      <pubDate>Thu, 05 Mar 2026 10:16:35 +0000</pubDate>
      <link>https://dev.to/chelsy/how-i-implemented-the-sm-2-spaced-repetition-algorithm-in-my-flashcard-app-54g9</link>
      <guid>https://dev.to/chelsy/how-i-implemented-the-sm-2-spaced-repetition-algorithm-in-my-flashcard-app-54g9</guid>
      <description>&lt;h2&gt;
  
  
  What Is Spaced Repetition?
&lt;/h2&gt;

&lt;p&gt;Here's the problem with traditional studying: you review everything equally, whether you know it or not. That's inefficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spaced repetition&lt;/strong&gt; flips this. Cards you struggle with come back sooner. Cards you know well get pushed further into the future. Over time, you spend your energy exactly where it's needed.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;SM-2 algorithm&lt;/strong&gt; (developed by Piotr Wozniak in 1987) is the math behind this. It's the same core logic used in Anki — one of the most popular study tools in the world.&lt;/p&gt;




&lt;h2&gt;
  
  
  📐 The SM-2 Formula Explained
&lt;/h2&gt;

&lt;p&gt;Every card tracks three values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repetitions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many times you've answered correctly in a row&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;easeFactor&lt;/code&gt; (EF)&lt;/td&gt;
&lt;td&gt;How "easy" the card is (starts at 2.5, min 1.3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;interval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Days until next review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After each review, the user rates their recall from &lt;strong&gt;0 to 5&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0–2&lt;/strong&gt; = Failed (didn't remember)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3&lt;/strong&gt; = Barely remembered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4&lt;/strong&gt; = Correct with some hesitation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5&lt;/strong&gt; = Perfect recall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the core logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sm2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// quality: 0-5 rating from the user&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Failed — reset streak, review again soon&lt;/span&gt;
    &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repetitions&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="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Passed — calculate next interval&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repetitions&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repetitions&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;easeFactor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repetitions&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Update ease factor&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;easeFactor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;easeFactor&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="nx"&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="nx"&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;span class="c1"&gt;// Ease factor never drops below 1.3&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;easeFactor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;easeFactor&lt;/span&gt; &lt;span class="o"&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="c1"&gt;// Set next review date&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextReviewAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextReviewAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextReviewAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The whole algorithm fits in about 20 lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Modeled This in the Database
&lt;/h2&gt;

&lt;p&gt;In my Laravel backend, each card record in the &lt;code&gt;cards&lt;/code&gt; table stores its SM-2 state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// database/migrations/create_cards_table.php&lt;/span&gt;

&lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cards'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Blueprint&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;foreignId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deck_id'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;constrained&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cascadeOnDelete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'front'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'back'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// SM-2 fields&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'repetitions'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ease_factor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'interval'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'next_review_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a study session ends, the frontend sends the user's rating to the API, and the backend runs the SM-2 calculation and updates these fields.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔁 The Study Session Flow
&lt;/h2&gt;

&lt;p&gt;Here's how a full study session works end-to-end:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Fetch due cards (Laravel Controller)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CardController.php&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getDueCards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$deckId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'deck_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$deckId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;whereNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'next_review_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;orWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'next_review_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cards&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cards are "due" if &lt;code&gt;next_review_at&lt;/code&gt; is null (never studied) or in the past.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. User studies &amp;amp; rates each card (Vue 3 frontend)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- StudySession.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"flipped = !flipped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"!flipped"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;currentCard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;front&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;currentCard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"flipped"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"rating-buttons"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"rating in [0, 1, 2, 3, 4, 5]"&lt;/span&gt;
              &lt;span class="na"&gt;:key=&lt;/span&gt;&lt;span class="s"&gt;"rating"&lt;/span&gt;
              &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"submitRating(rating)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nf"&gt;ratingLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useStudyStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/stores/studyStore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStudyStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentCard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentCard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;submitRating&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;flipped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rateCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentCard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ratingLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Blackout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wrong&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Wrong (familiar)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Good&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Easy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Backend applies SM-2 and saves
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// StudySessionController.php&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;rateCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$cardId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cardId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'quality'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0-5&lt;/span&gt;

    &lt;span class="c1"&gt;// Run SM-2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$quality&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repetitions&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="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;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="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repetitions&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;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="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repetitions&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;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;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ease_factor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repetitions&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$newEF&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ease_factor&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="nv"&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="nv"&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;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ease_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&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="nv"&gt;$newEF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next_review_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$card&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I Learned Building This
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. SM-2 is deceptively simple.&lt;/strong&gt; The algorithm itself is tiny — the complexity is in building the session UX around it (card flipping, ratings, progress tracking).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The ease factor is the secret sauce.&lt;/strong&gt; Cards you consistently ace get pushed further and further apart automatically. Cards you keep failing stay frequent. You don't need to configure anything — it self-adjusts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. "Due cards" queries need an index.&lt;/strong&gt; Once you have thousands of cards, querying by &lt;code&gt;next_review_at&lt;/code&gt; gets slow. I added a database index on that column and it made a noticeable difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'next_review_at'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Failed cards should NOT reset interval to 0.&lt;/strong&gt; A common mistake is setting &lt;code&gt;interval = 0&lt;/code&gt; on failure, which means the card shows up immediately in the same session forever. Setting it to &lt;code&gt;1&lt;/code&gt; (review tomorrow) feels much better in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The SM-2 algorithm is from 1987 — it works, but there are newer alternatives. The &lt;strong&gt;FSRS algorithm&lt;/strong&gt; (Free Spaced Repetition Scheduler) is a modern neural-network-based approach that reportedly outperforms SM-2. It's on my roadmap to implement next.&lt;/p&gt;

&lt;p&gt;If you're curious about the full project — including AI card generation with Groq LLaMA, community decks, and Google OAuth — the repo is here: &lt;a href="https://github.com/chelsynew72/flashcard-app" rel="noopener noreferrer"&gt;github.com/chelsynew72/flashcard-app&lt;/a&gt; and the live demo is at &lt;a href="https://flashcard-app-five-gamma.vercel.app" rel="noopener noreferrer"&gt;flashcard-app-five-gamma.vercel.app&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Have you built anything with spaced repetition? I'd love to hear how you approached it — drop a comment below! &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Next.js Finally Made Caching Make Sense — At Least to Me</title>
      <dc:creator>tem chelsy</dc:creator>
      <pubDate>Fri, 20 Feb 2026 15:07:26 +0000</pubDate>
      <link>https://dev.to/chelsy/nextjs-finally-made-caching-make-sense-at-least-to-me-3jfg</link>
      <guid>https://dev.to/chelsy/nextjs-finally-made-caching-make-sense-at-least-to-me-3jfg</guid>
      <description>&lt;p&gt;Okay so I have to be honest. When I first started learning Next.js, caching was the thing that broke my brain the most. I'd make a change to my data, refresh the page in production, and nothing would update. I'd Google it, find some Stack Overflow answer about &lt;code&gt;revalidatePath&lt;/code&gt; or &lt;code&gt;no-store&lt;/code&gt;, paste it in, and sometimes it worked. Sometimes it didn't. I never really understood &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Turns out I wasn't alone. A lot of developers — including experienced ones — found Next.js caching confusing. And with Next.js 16, the team actually did something about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was the Problem?
&lt;/h2&gt;

&lt;p&gt;In older versions of Next.js, caching happened automatically behind the scenes. Your pages were cached by default, and if you wanted fresh data, you had to know exactly which option to add, where to add it, and in what kind of file. The framework was making decisions for you without really telling you.&lt;/p&gt;

&lt;p&gt;For a junior dev like me, that's really hard to debug. You don't know if the problem is your code, your fetch call, your deployment, or the framework itself quietly doing something you didn't ask for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Way: &lt;code&gt;"use cache"&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Next.js 16 introduces something called Cache Components, and the main idea is just one line: &lt;code&gt;"use cache"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You put it at the top of a component, a page, or even a single function — and that's what gets cached. If you don't write it, the page runs fresh on every request by default. That's it. You're in control now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;BlogPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/article&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For me, this is so much easier to reason about. I can look at any file and immediately know: does it have &lt;code&gt;"use cache"&lt;/code&gt; or not? That's my answer. No more digging through docs trying to figure out why my data is stale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pages Can Now Be Both Fast and Fresh
&lt;/h2&gt;

&lt;p&gt;There's also something called Partial Pre-Rendering, and Next.js 16 is where it actually starts to click.&lt;/p&gt;

&lt;p&gt;Here's the idea: imagine you have a blog post page. Most of it never changes — the title, the text, the author. But there's a comment counter at the bottom that should always be live. Before, you had to choose: make the whole page fast and static, or make the whole page dynamic and slower. There was no in-between.&lt;/p&gt;

&lt;p&gt;Now you can cache the parts that don't change and let the live parts load in separately. The page still loads instantly. The comment counter catches up right after. Users get both.&lt;/p&gt;

&lt;p&gt;That's genuinely cool and something I struggled to do cleanly before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev Server Is So Much Faster Now
&lt;/h2&gt;

&lt;p&gt;Next.js 16 also makes Turbopack the default bundler. I didn't fully understand what a bundler was when I started, but what I do know is that my dev server used to take a while to start and Fast Refresh sometimes felt slow on bigger projects.&lt;/p&gt;

&lt;p&gt;With Turbopack, it's noticeably faster. And now it caches stuff between restarts, so when you stop and start your dev server, it doesn't have to redo everything from scratch. Small thing, but when you're restarting your server ten times a day while debugging, you feel it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Less Boilerplate With the React Compiler
&lt;/h2&gt;

&lt;p&gt;This one I'm still wrapping my head around, but basically — if you've ever been told to use &lt;code&gt;useMemo&lt;/code&gt; or &lt;code&gt;useCallback&lt;/code&gt; to stop your components from re-rendering too much, Next.js 16 includes the React Compiler which handles a lot of that automatically.&lt;/p&gt;

&lt;p&gt;I'll be honest, I've copy-pasted &lt;code&gt;useCallback&lt;/code&gt; without fully understanding it more than once. Knowing the compiler can handle some of that optimization for me is reassuring.&lt;/p&gt;




&lt;p&gt;I'm still learning Next.js every day, and I don't have all the answers. But Next.js 16 is the first version where I feel like the framework is working &lt;em&gt;with&lt;/em&gt; me instead of quietly doing things I can't see. The caching changes alone would have saved me hours of confusion when I was starting out.&lt;/p&gt;

&lt;p&gt;If you're also a junior dev trying to figure this stuff out — yeah, it was confusing before. It's better now.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>nextjs</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
