For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/guide/state-transitions.md.

State transitions

State describes the card's current FSRS learning phase. A card created with scheduler.newCard() starts in New. On each review, DefaultScheduler combines the FSRS model result with the learning-step policy to determine the next state and due time.

DefaultScheduler state transition flowchart

The diagram and table show the final scheduled interval, after explicit steps and interval middleware. Ratings affect that interval; they do not alone determine the next state.

graduatingInterval (default 1 day) is the shortest interval for a card in Review. A final interval shorter than graduatingInterval enters or keeps Learning/Relearning; an interval of graduatingInterval or longer enters Review. This also applies when steps are disabled or empty and the model supplies the interval.

An exception applies when Again uses a model interval to override an explicit step: the card stays in Learning/Relearning, even if the final interval reaches graduatingInterval. The diagram shows the general interval-based mapping; this exception takes precedence.

Zero-length steps (such as 0m, or durations that round to zero seconds) count as no step. Negative durations are not supported.

What each state means

StateMeaningDefault queue
NewThe card is new or has been reset with scheduler.forget().new
LearningThe card is in it's initial learning state and needs another review. It will stay in it's learning state so long as the next step is shorter than graduatingInterval.learning
ReviewThe card is outside short-term learning; its interval is at least graduatingInterval.review
RelearningA previously graduated card is in a learning state again and needs another review. It will stay in a relearning state so long as it's interval is shorter than graduatingInterval.learning

How ratings move a card

Ratings first determine whether an explicit step supplies the interval. The table below assumes enableShortTerm: true and a nonempty step list for the current state: learningSteps for New/Learning, or relearningSteps for Review/Relearning. If that list is empty, the scheduler uses the model interval without a graduation minimum, even if the other list has steps.

The graduation behavior below assumes maximumInterval >= graduatingInterval and no other middleware shortens the interval below graduatingInterval.

RatingNew / Learning / RelearningReview
AgainReturn to the first step; reset learningStep to 0.Use the first relearning step; reset learningStep to 0.
HardKeep the current learningStep; use the Hard step interval.Use the model interval, but at least graduatingInterval; stay in Review.
GoodAdvance when a next step exists; otherwise graduate to Review with the model interval, but at least graduatingInterval.Use the model interval, but at least graduatingInterval; stay in Review.
EasySkip explicit steps and graduate to Review with the model interval, but at least graduatingInterval.Use the model interval, but at least graduatingInterval; stay in Review.

The Hard step interval is the average of the first two steps, or 1.5 times the only step. With empty steps or enableShortTerm: false, every rating uses the model interval, and only the state mapping below applies. learningStep keeps the next step only while the card stays in Learning/Relearning; it resets to 0 when the card enters Review.

When allowModelOverride is enabled (the FSRS-7 default in DefaultScheduler), the scheduler uses the interval from the model instead of the current step if the interval from the model is longer. For Again, the model's interval is capped at graduatingInterval, and an Again that uses it always enters Learning/Relearning. For Hard and Good, a card whose interval reaches graduatingInterval graduates to Review without finishing the remaining steps. Interval middleware can still adjust model intervals.

If maximumInterval < graduatingInterval, the cap can prevent graduation. For example, with graduatingInterval: 2 and maximumInterval: 1, finishing the learning steps produces a model-based interval capped at 1 day, so the card stays in Learning.

After the interval is resolved, schedulerLearningStepsMiddleware uses the table below to determine the next state. The Again override exception described above takes precedence. The table shows state transitions, not how each rating selects an interval:

Current stateFinal interval < graduatingIntervalFinal interval ≥ graduatingInterval
NewLearningReview
LearningLearningReview
ReviewRelearningReview
RelearningRelearningReview

When an explicit step applies, the scheduler keeps its configured duration unless the model from allowModelOverride provides a longer interval. A step of graduatingInterval or longer still moves the card to Review.

Learning states require the

schedulerLearningStepsMiddleware middleware DefaultScheduler registers a schedulerLearningStepsMiddleware and defaults enableShortTerm to true including for FSRS-7. For FSRS-7, this flag controls explicit learningSteps and relearningSteps, not its intrinsic fast trace or the interval-based state mapping. With enableShortTerm: false, an interval below graduatingInterval still enters the Learning or Relearning phase.

If you create a custom scheduler with defineScheduler, register the middleware with .use(schedulerLearningStepsMiddleware) to get this state mapping; without it, the default behaviour remains Review with scheduleStatus: review, even when FSRS-7 returns an interval below one day. The model computes memory states and intervals; learning-state transitions are middleware behavior. To produce Learning/Relearning states after FSRS-7 scheduling, register schedulerLearningStepsMiddleware or implement your own state-transition middleware.

Rating.Manual is not a review grade. Pass one of Again, Hard, Good, or Easy to review().

Run the transitions

The runnable example below uses the default FSRS-7 model with this configuration:

const scheduler = await DefaultScheduler({
  learningSteps: ['1m', '10m'],
  relearningSteps: ['10m'],
  enableFuzz: false,
  allowModelOverride: false,
})

graduatingInterval uses its default of 1 day. The card starts in New with learningStep: 0. learningStep is a zero-based step index, not a review count; the same field tracks progress through either the learning or relearning steps.

With allowModelOverride: false, the example produces these transitions:

Current stateCurrent learningStepRatingResulting stateResulting learningStepInterval
New0AgainLearning01 minute
Learning0GoodLearning110 minutes
Learning1HardLearning15.5 minutes
Learning1AgainLearning01 minute
Learning1GoodReview01 day
Review0AgainRelearning010 minutes
Relearning0GoodReview01 day

The Hard, Again, and Good rows at learningStep: 1 are alternative ratings of the same card, not consecutive reviews. The relearning branch starts from a separate card that reached Review via Easy.

Disabling allowModelOverride keeps the configured step intervals. If enabled, a longer model interval can replace an explicit step. For example, a hypothetical 30-minute model interval would extend the 10-minute Good step to 30 minutes. A shorter model interval would leave that step unchanged. The scheduler still uses model intervals after the steps finish or when no step applies, even with allowModelOverride: false.

The full example below uses the public DefaultSchedulerCard, Grade, and State types. Its output shows intervals in minutes rounded to two decimals.

dueAt is not now

dueAt is when the card becomes due, while now is the actual review time and should be equal to or later than dueAt. To keep the example deterministic without conflating them, the helper reviews each card 30 seconds after dueAt.

import {
  
function DefaultScheduler<Version extends DefaultSchedulerVersion = "FSRS-7">(options?: DefaultSchedulerOptions<Version>): Promise<DefaultScheduler<Version>>
type DefaultScheduler<Version extends DefaultSchedulerVersion = "FSRS-7"> = (Version extends "FSRS-7" ? Omit<SchedulerCore<SchedulerCoreEnv<SchedulerEnvFor<FSRS7DefaultSchedulerModel, Chrono<{
    readonly time: SRSSchema<{
        input: Date;
        output: Date;
    }>;
    readonly config: SRSSchema<{
        input: {
            readonly fractionalDays?: boolean | undefined;
        };
        output: {
            readonly fractionalDays: boolean;
        };
    }>;
    readonly fields: {
        readonly card: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
            };
        }>;
        readonly revlog: SRSSchema<{
            input: {
                dueAt: Date;
                lastReviewAt?: (Date | null) | undefined;
                reviewTime: Date;
            };
            output: {
                dueAt: Date;
                lastReviewAt: Date | null;
                reviewTime: Date;
            };
        }>;
    };
}>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>, "definition"> : Omit<...> & {
    ...;
}) & {
    ...;
}
DefaultScheduler
,
type
type DefaultSchedulerCard<Version extends DefaultSchedulerVersion = "FSRS-7"> = Version extends "FSRS-7" ? {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
} : {
    cardId: string | number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
DefaultSchedulerCard
,
type
type Grade = 1 | 2 | 3 | 4
Grade
,
type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
,
type State = 0 | 1 | 2 | 3
const State: Readonly<{
    readonly New: 0;
    readonly Learning: 1;
    readonly Review: 2;
    readonly Relearning: 3;
}>
State
,
} from 'ts-fsrs' const
const scheduler: DefaultScheduler<"FSRS-7">
scheduler
= await
DefaultScheduler<"FSRS-7">(options?: DefaultSchedulerOptions<"FSRS-7"> | undefined): Promise<DefaultScheduler<"FSRS-7">>
DefaultScheduler
({
DefaultSchedulerOptions<"FSRS-7">.learningSteps?: readonly (`${number}d` | `${number}m` | `${number}h`)[] | undefined

Defaults to ['1m', '10m'].

learningSteps
: ['1m', '10m'],
DefaultSchedulerOptions<"FSRS-7">.relearningSteps?: readonly (`${number}d` | `${number}m` | `${number}h`)[] | undefined

Defaults to ['10m'].

relearningSteps
: ['10m'],
DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.enableFuzz?: boolean | undefined
enableFuzz
: false,
DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.allowModelOverride?: boolean | undefined

Let longer model intervals override learning steps; defaults to true for FSRS-7 only.

allowModelOverride
: false,
}) const stateNames: Record<
type State = 0 | 1 | 2 | 3
State
, string> = {
[
const State: Readonly<{
    readonly New: 0;
    readonly Learning: 1;
    readonly Review: 2;
    readonly Relearning: 3;
}>
State
.New]: 'New',
[
const State: Readonly<{
    readonly New: 0;
    readonly Learning: 1;
    readonly Review: 2;
    readonly Relearning: 3;
}>
State
.Learning]: 'Learning',
[
const State: Readonly<{
    readonly New: 0;
    readonly Learning: 1;
    readonly Review: 2;
    readonly Relearning: 3;
}>
State
.Review]: 'Review',
[
const State: Readonly<{
    readonly New: 0;
    readonly Learning: 1;
    readonly Review: 2;
    readonly Relearning: 3;
}>
State
.Relearning]: 'Relearning',
} const
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
= (
card: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
card
:
type DefaultSchedulerCard<Version extends DefaultSchedulerVersion = "FSRS-7"> = Version extends "FSRS-7" ? {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
} : {
    cardId: string | number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
DefaultSchedulerCard
, grade:
type Grade = 1 | 2 | 3 | 4
Grade
) => {
const reviewedAt = new Date(
card: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
card
.dueAt.getTime() + 30_000)
return
const scheduler: DefaultScheduler<"FSRS-7">
scheduler
.
review: (input: {
    readonly card: Readonly<Omit<Readonly<{
        stabilityFast: number;
        stability: number;
        difficulty: number;
    } & {
        dueAt: Date;
        lastReviewAt: Date | null;
    } & {
        readonly cardId: string | number;
        readonly reps: number;
        readonly lapses: number;
        readonly scheduledDays: number;
        readonly learningStep?: number | undefined;
    } & SchedulerCoreFields$1<"review" | "new" | "learning">>, keyof SchedulerCoreFields$1<"review" | "new" | "learning">> & SchedulerCoreFields$1<"review" | "new" | "learning">>;
    readonly grade: Grade;
    readonly now?: Date | undefined;
}) => {
    ...;
}
review
({ card, grade, now: reviewedAt }).
card: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
card
} const
const newCard: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
newCard
=
const scheduler: DefaultScheduler<"FSRS-7">
scheduler
.
newCard: (options?: ({
    readonly cardId?: string | number | undefined;
} & {
    readonly now?: Date | undefined;
}) | undefined) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
newCard
({
cardId: 'state-transitions', now: new Date('2026-01-01T00:00:00.000Z'), }) const
const learning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
learning
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const newCard: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
newCard
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Again)
const
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const learning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
learning
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good)
const
const afterLearningSteps: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
afterLearningSteps
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good)
const
const repeated: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
repeated
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Hard)
const
const restarted: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
restarted
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Again)
const
const reviewed: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewed
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const newCard: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
newCard
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Easy)
const
const relearning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
relearning
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const reviewed: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewed
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Again)
const
const afterRelearningSteps: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
afterRelearningSteps
=
const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewAfterDue
(
const relearning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
relearning
,
const Rating: Readonly<{
    readonly Manual: 0;
    readonly Again: 1;
    readonly Hard: 2;
    readonly Good: 3;
    readonly Easy: 4;
}>
Rating
.Good)
for (const [
const from: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
} | {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
from
, rating,
const to: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
to
] of [
[
const newCard: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
newCard
, 'Again',
const learning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
learning
],
[
const learning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
learning
, 'Good',
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
],
[
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
, 'Hard',
const repeated: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
repeated
],
[
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
, 'Again',
const restarted: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
restarted
],
[
const nextLearningStep: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
nextLearningStep
, 'Good',
const afterLearningSteps: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
afterLearningSteps
],
[
const newCard: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
newCard
, 'Easy',
const reviewed: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewed
],
[
const reviewed: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
reviewed
, 'Again',
const relearning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
relearning
],
[
const relearning: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
relearning
, 'Good',
const afterRelearningSteps: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
afterRelearningSteps
],
] as
type const = readonly [readonly [{
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}, "Again", {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}], readonly [{
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}, "Good", {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}], ... 5 more ..., readonly [...]]
const
) {
console.log( `${stateNames[
const from: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
} | {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
from
.state]} --${rating}--> ${stateNames[
const to: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
to
.state]}; ` +
`learningStep: ${
const from: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
} | {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
from
.learningStep} -> ${
const to: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
to
.learningStep}; ` +
`interval: ${(
const to: {
    cardId: string | number;
    stabilityFast: number;
    stability: number;
    difficulty: number;
    dueAt: Date;
    lastReviewAt: Date | null;
    reps: number;
    lapses: number;
    scheduledDays: number;
    learningStep: number;
    state: State;
    scheduleStatus: "review" | "new" | "learning";
}
to
.scheduledDays * 1440).toFixed(2)}m`
) }

Card, revlog, and memory state

  • The input card is the state before a review; review() does not mutate it.
  • result.card contains the next state, next FSRS memory state (stability, stabilityFast, and difficulty for FSRS-7), and next dueAt.
  • result.revlog records the previous state and memory state together with the selected rating and review timing. That snapshot is also what rollback uses to restore the card.
  • scheduleStatus controls queue membership. By default it maps New to new, both learning states to learning, and Review to review; middleware may add statuses such as suspended without changing the four FSRS states.

Use card.state to find the learning phase, and card.scheduleStatus to filter scheduling queues.