A pure TypeScript implementation of the Free Spaced Repetition Scheduler (FSRS) v4.5 algorithm with optional v6 support.
FSRS is a modern, evidence-based spaced repetition algorithm that optimizes review intervals based on the DSR (Difficulty, Stability, Retrievability) model of memory. Unlike traditional algorithms like SM-2, FSRS uses trainable parameters optimized on real user data to achieve superior retention prediction.
- FSRS v4.5 Core: Proven algorithm with stable defaults (17 parameters)
- 81% Better Than SM-2: Captures 98% of v6's improvement with fewer parameters
- Edge Runtime Ready: Works in Cloudflare Workers, Vercel Edge, Deno Deploy
- Continuous Grading: Supports both discrete (1-4) and continuous (1.0-4.0) ratings
- Auto-Rating: Built-in response time to grade conversion
- Optional v6 Support: Accepts optimized 21-parameter sets for advanced users
- Type-Safe: Full TypeScript support with strict typing
- Zero Dependencies: Minimal bundle size
- Pure Functions: Immutable API, no side effects
npm install @squeakyrobot/fsrsimport { FSRS, Rating } from '@squeakyrobot/fsrs';
// Initialize with default v4.5 parameters
const fsrs = new FSRS();
// Create a new card
let card = fsrs.createEmptyCard();
// Simulate a review session - get all 4 possible outcomes
const now = new Date();
const reviewResults = fsrs.repeat(card, now);
// User rates the card as "Good"
const { card: updatedCard, log } = reviewResults[Rating.Good];
console.log(`Next review in ${updatedCard.scheduled_days} days`);
console.log(`Current stability: ${updatedCard.stability} days`);
console.log(`Difficulty: ${updatedCard.difficulty}/10`);import { FSRS } from '@squeakyrobot/fsrs';
const fsrs = new FSRS();
let card = fsrs.createEmptyCard();
// Track response time
const start = Date.now();
// ... user answers flashcard ...
const responseTime = Date.now() - start;
// Convert response time to grade (1.0-4.0)
const averageTime = 2000; // Expected average response time (ms)
const grade = fsrs.autoRating(responseTime, averageTime, card.difficulty);
// Schedule with continuous grade
const { card: updatedCard, log } = fsrs.scheduleWithGrade(card, grade);// worker.ts
import { FSRS } from '@squeakyrobot/fsrs';
const fsrs = new FSRS();
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { card, responseTime } = await request.json();
const grade = fsrs.autoRating(responseTime, 2000);
const result = fsrs.scheduleWithGrade(card, grade);
return Response.json(result);
}
};For complete mathematical details, see the Algorithm Reference.
FSRS models memory using three distinct variables:
- Difficulty (D): Inherent complexity of the item (1.0 = easiest, 10.0 = hardest)
- Stability (S): Time interval (days) for retrievability to decay from 100% to 90%
- Retrievability (R): Instantaneous probability of successful recall (0-1)
V4.5 provides the optimal balance for a minimal implementation:
- Proven defaults: Works excellently without requiring parameter optimization
- 98% of v6's benefit: Only 3% RMSE difference from v6 on benchmark data
- Simpler: No same-day review complexity
- Stable: Widely deployed with deterministic behavior
V6 support is available for advanced users who:
- Run the FSRS optimizer on their review history
- Need personalized parameters (w0-w20)
- Want the marginal 2-3% accuracy improvement
For complete API documentation, see the Full API Reference.
constructor(params?: Partial<FSRSParameters>)Creates a new FSRS scheduler instance with optional parameter overrides.
Calculates scheduling outcomes for all possible ratings. Useful for showing users all 4 button options with predicted intervals.
Parameters:
card: Current card statenow: Current timestamp (default: current time)
Returns: Map of Rating -> { log, card } for all 4 ratings
Schedules a review with a specific grade. Primary method for auto-rating scenarios and manual reviews.
Parameters:
card: Current card stategrade: Rating (discrete 1-4) or continuous (1.0-4.0)now: Current timestamp (default: current time)
Returns: { log, card } with updated card state and review log
Auto-rating utility that converts response time to a continuous grade.
Parameters:
responseTime: Time taken to respond (milliseconds)averageTime: Expected average response time (milliseconds)difficulty: Optional card difficulty (1-10) for adjustment (default: 5.5)
Returns: Continuous rating (1.0-4.0)
Formula:
- Much faster than average: approaches 4.0 (Easy)
- Around average: approaches 3.0 (Good)
- Slower than average: approaches 2.0 (Hard) or 1.0 (Again)
Calculates current retrievability (probability of recall).
Parameters:
card: Card to evaluatenow: Current timestamp (default: current time)
Returns: Retrievability (0-1)
Creates a new empty card ready for first review.
Parameters:
now: Optional timestamp (default: current time)
Returns: New card in "New" state
Gets the next interval for a given rating (preview mode).
Parameters:
card: Card to evaluaterating: Rating to previewnow: Current timestamp (default: current time)
Returns: Interval in days
type Rating = 1 | 2 | 3 | 4; // Again | Hard | Good | Easy
const Rating = {
Again: 1,
Hard: 2,
Good: 3,
Easy: 4,
} as const;type State = 0 | 1 | 2 | 3; // New | Learning | Review | Relearning
const State = {
New: 0,
Learning: 1,
Review: 2,
Relearning: 3,
} as const;interface Card {
due: Date; // Next scheduled review timestamp
stability: number; // Days to 90% retention
difficulty: number; // Complexity (1-10)
elapsed_days: number; // Days since last review
scheduled_days: number; // Assigned interval
reps: number; // Total review count
lapses: number; // Failure count
state: State; // Current learning phase
last_review: Date | null; // Previous review timestamp
}interface FSRSParameters {
request_retention: number; // 0.7-0.97, default 0.9
maximum_interval: number; // Default 36500 days
w: number[]; // 17 (v4.5) or 21 (v6) weights
enable_fuzz: boolean; // Random interval jitter
enable_short_term?: boolean; // V6 same-day logic
}Checks if a grade represents a lapse (failure to recall). Useful for analytics, streak tracking, and UI badges.
Parameters:
grade: Rating (discrete or continuous)
Returns: true if grade < 1.5 (Again)
const fsrs = new FSRS({
request_retention: 0.9,
maximum_interval: 36500,
enable_fuzz: true
});For users with optimized parameters from the FSRS optimizer:
const fsrs = new FSRS({
request_retention: 0.9,
w: [
// Your 21 optimized parameters from FSRS optimizer
0.4, 0.6, 2.4, 5.8, 4.93, 0.94, 0.86, 0.01,
1.49, 0.14, 0.94, 2.18, 0.05, 0.34, 1.26,
0.29, 2.61, 0.48, 1.05, 0.36, -0.54
]
});Note: V6 parameters only provide benefit when optimized on your specific review data. Generic v6 defaults offer minimal improvement over v4.5.
import create from 'zustand';
import { FSRS, Card, Rating } from '@squeakyrobot/fsrs';
const fsrs = new FSRS();
interface FlashcardStore {
cards: Map<string, Card>;
reviewCard: (id: string, rating: Rating) => void;
}
const useFlashcards = create<FlashcardStore>((set) => ({
cards: new Map(),
reviewCard: (id, rating) => set((state) => {
const card = state.cards.get(id);
if (!card) return state;
const result = fsrs.repeat(card, new Date());
const updatedCard = result[rating].card;
return {
cards: new Map(state.cards).set(id, updatedCard)
};
})
}));import { FSRS, Rating } from '@squeakyrobot/fsrs';
import { NextRequest, NextResponse } from 'next/server';
const fsrs = new FSRS();
export async function POST(req: NextRequest) {
const { card, rating } = await req.json();
const result = fsrs.repeat(card, new Date());
const updated = result[rating as Rating];
return NextResponse.json({
card: updated.card,
log: updated.log
});
}Based on the fsrs-benchmark dataset (1.7B reviews from 20K users):
| Algorithm | Log Loss | RMSE | Improvement vs SM-2 |
|---|---|---|---|
| SM-2 (Anki Default) | 0.7317 | 0.4066 | - |
| FSRS v4.5 | 0.3624 | 0.0764 | ~81% |
| FSRS v6 | 0.3460 | 0.0653 | ~84% |
Key Insight: FSRS v4.5 captures 98% of v6's improvement with 19% fewer parameters, making it ideal for applications that don't have user-specific optimization.
Contributions are welcome! See the CONTRIBUTING guide for development guidelines.
MIT License - see LICENSE for details.