npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@monkey-dev-vibes/spaced-repetition

v0.1.0

Published

Modern TypeScript implementation of the SM-2 spaced repetition algorithm. Pure function, zero runtime dependencies, full test coverage.

Readme

spaced-repetition

CI license types zero deps

A modern, audit-friendly TypeScript implementation of the SM-2 spaced repetition algorithm. One pure function. Zero runtime dependencies. Ships exactly what you need to schedule reviews — and nothing else.

import { createNewCard, processReview } from '@monkey-dev-vibes/spaced-repetition';

let card = createNewCard();              // immediately due
card = processReview(card, 2);           // Good → due in 1 day
card = processReview(card, 2);           // Good → due in 6 days
card = processReview(card, 3);           // Easy → due in ~16 days

card.interval;       // 16
card.easeFactor;     // 2.7
card.nextReviewDate; // ISO 8601 timestamp

That's the whole API for the common case. No setup, no class hierarchy, no global state.

Why this one?

Searching npm for sm2 returns half-a-dozen packages, most of which haven't shipped a release in years. The ones that are maintained tend to bundle storage adapters, React hooks, or framework assumptions. None offer the combination of:

| Feature | This package | Most others | | --------------------------------------------- | :----------: | -------------------------- | | TypeScript types | ✓ | partial / @types only | | Pure function — (card, rating) → card | ✓ | mixed with storage | | Zero runtime dependencies | ✓ | usually bundles a DB / ORM | | Dual ESM + CJS output | ✓ | one or the other | | Non-mutation purity test | ✓ | rare | | Configurable max interval | ✓ | usually hardcoded | | Honest FSRS comparison | ✓ | rarely mentioned |

If you want the scheduling math and nothing else, this is the package.

At a glance

  • Pure function. processReview(card, rating) returns a brand-new card. No mutation, no IO, no side effects, no randomness. Deterministic given the same inputs.
  • Zero runtime dependencies. The whole package is ~95 lines of TypeScript. Audit it in a single sitting.
  • Dual ESM + CJS. Works in modern bundlers, legacy CommonJS, edge runtimes, and Node ≥ 18.
  • Configurable interval cap. Default is Infinity (classical SM-2). Pass maxInterval: 180 for exam-style time-bounded study.
  • Documented policy choices. Where this differs from the 1990 paper (4-grade scale, optional cap) is called out openly so you can decide for yourself.
  • Type-safe rating scale. Rating is the literal union 0 | 1 | 2 | 3 — invalid grades fail at compile time.

Install

npm install @monkey-dev-vibes/spaced-repetition

Node 18+. No peer dependencies. No native modules. Works in browsers, Workers, Deno, and Bun.

Usage

Schedule a review

import { createNewCard, processReview } from '@monkey-dev-vibes/spaced-repetition';

let card = createNewCard();

// User answered: 0=Again, 1=Hard, 2=Good, 3=Easy
card = processReview(card, 2);

Cap the maximum interval

Classical SM-2 has no upper bound — after dozens of consecutive successful reviews, intervals balloon into years. For time-bounded study (an exam, a certification window, a six-month course), cap the interval:

card = processReview(card, 3, new Date(), { maxInterval: 180 });

Check whether a card is due

import { isDue } from '@monkey-dev-vibes/spaced-repetition';

if (isDue(card)) {
  // surface the card to the user
}

Persist your cards however you like

The package is storage-agnostic on purpose. A CardState is a plain serialisable object — you can stash it in localStorage, IndexedDB, SQLite, Postgres, a JSON file, or sync it to a cloud KV store. Whatever fits your stack.

// localStorage example
localStorage.setItem(cardId, JSON.stringify(card));
const restored: CardState = JSON.parse(localStorage.getItem(cardId)!);

API

createNewCard(now?: Date): CardState

Returns a fresh card with default ease factor 2.5, interval 0, repetitions 0, and nextReviewDate equal to now. The card is immediately due.

processReview(card, rating, now?, options?): CardState

Runs one SM-2 review. Returns a new CardState; the input is not mutated.

| Argument | Type | Default | Description | | --- | --- | --- | --- | | card | CardState | required | Current state. | | rating | Rating | required | 0 Again, 1 Hard, 2 Good, 3 Easy. | | now | Date | new Date() | Review timestamp. | | options.maxInterval | number | Infinity | Upper bound on the computed interval, in days. |

isDue(card, now?): boolean

Convenience helper: now.toISOString() >= card.nextReviewDate.

MASTERED_INTERVAL_DAYS

Constant 21. Cards with interval > MASTERED_INTERVAL_DAYS are commonly displayed as "mastered" in progress UIs. Not part of the SM-2 specification — ignore or override as you wish.

Types

interface CardState {
  easeFactor: number;     // starts at 2.5, floored at 1.3
  interval: number;       // days until next review
  repetitions: number;    // consecutive successful reviews (Good/Easy)
  nextReviewDate: string; // ISO 8601
}

type Rating = 0 | 1 | 2 | 3;

interface ReviewOptions {
  maxInterval?: number;
}

The algorithm in plain English

  1. Grade the recall on the 4-level scale. Again / Hard / Good / Easy map to SM-2 qualities 1 / 2 / 4 / 5.
  2. On a pass (Good or Easy), increment the repetition count and compute the next interval:
    • Rep 1 → 1 day
    • Rep 2 → 6 days
    • Rep ≥ 3 → round(previous_interval × ease_factor), optionally capped.
  3. On a lapse (Again or Hard), reset repetitions to 0 and interval to 1 day.
  4. Update the ease factor on every review (pass or lapse): EF ← EF + (0.1 − (5 − q)(0.08 + (5 − q)·0.02)), floored at 1.3.
    • Easy → +0.10
    • Good → ±0.00
    • Hard → −0.32
    • Again → −0.54

That's it. The whole algorithm is a closed-form arithmetic update over four numbers.

Policy decisions

These are deliberate deviations from the canonical 1990 specification. They are documented openly so you can decide whether they fit your use case.

Four-grade scale, not six

Canonical SM-2 grades recall on 0–5. This implementation exposes four user-visible grades and maps them to qualities {1, 2, 4, 5}. Qualities 0 ("total blackout", EF delta −0.80) and 3 ("hesitation but correct, no reset", EF delta −0.14) are not surfaced.

Practical effect: the harshest single-review penalty is −0.54, not −0.80. Hard always triggers a streak reset. This matches the simplified Anki-style four-button layout most modern flashcard apps present, where forcing users to distinguish six levels of recall difficulty hurts more than it helps.

Optional interval cap

Canonical SM-2 has no upper bound. This implementation defaults to Infinity (matching the paper) but accepts an explicit maxInterval option for time-bounded use cases. Pass maxInterval: 180 for a six-month review horizon.

Should you use SM-2 or FSRS?

SM-2 is a 1990 algorithm. It's widely understood, predictable, requires no training data, and is trivial to audit. It also has known weaknesses: the ease factor is a global estimate that doesn't adapt to forgetting patterns over time, and the algorithm cannot predict the probability of recall at a given moment.

If you want state-of-the-art scheduling, use ts-fsrs (FSRS v4/v5). FSRS uses a neural-derived forgetting model that significantly outperforms SM-2 on recall accuracy benchmarks.

Use this package when:

  • You need a dependency-free, auditable algorithm you can hold in your head.
  • You're building for a constrained environment (embedded, edge, offline-first, low-end devices).
  • Your team understands SM-2 and wants predictable, explainable scheduling.
  • You want minimal surface area and explicit policy boundaries.
  • You're translating an existing SM-2 implementation to TypeScript and need an algorithmic peer.

Use FSRS instead when:

  • You have meaningful review-history data and want to optimise recall accuracy.
  • You're willing to take on a slightly more complex API for measurable scheduling gains.

Testing

npm test

21 tests across the rating scale, interval boundaries, ease factor floor, purity (non-mutation), the maxInterval option, and isDue. Runs in ~1 second. Spends zero external API tokens.

Contributing

PRs welcome. See CONTRIBUTING.md for the three load-bearing rules — processReview stays pure, behaviour changes update both JSDoc and the Policy decisions section, new defaults match the canonical SM-2 paper.

License

MIT