The Core Problem

A lot of language apps feel good in demos but collapse as soon as you add spaced repetition, cross-device sync, and mixed-script content. The fix is not “more features.” The fix is better state boundaries.

Let’s build a model that keeps card state predictable while the UI stays fast.

Step 1: Split content from learning state

Your vocabulary entry should not carry scheduling internals. Separate static content from progress state.

struct Lexeme: Identifiable, Codable {
    let id: String
    let term: String
    let reading: String
    let meaning: String
}

struct LearningState: Codable {
    var intervalDays: Int
    var ease: Double
    var dueAt: Date
}

Step 2: Make scheduling rules explicit

Think of your scheduler as a coach. Good answer: push next drill further away. Bad answer: bring it back sooner.

func scheduleNext(_ state: LearningState, grade: Int, now: Date) -> LearningState {
    var next = state
    if grade >= 4 {
        next.intervalDays = max(1, Int(Double(state.intervalDays) * state.ease))
        next.ease = min(2.8, state.ease + 0.05)
    } else {
        next.intervalDays = 1
        next.ease = max(1.3, state.ease - 0.2)
    }
    next.dueAt = Calendar.current.date(byAdding: .day, value: next.intervalDays, to: now)!
    return next
}

Step 3: Build a session queue from due cards only

func dueQueue(lexemes: [Lexeme], states: [String: LearningState], now: Date) -> [Lexeme] {
    lexemes.filter { item in
        guard let s = states[item.id] else { return true }
        return s.dueAt <= now
    }
}

Step 4: Add offline-first writes

When network is unstable, persist locally first and sync in background. Never block study flow on a request timeout.

Pitfalls to Avoid

  • Using one giant view model for content, settings, and schedule logic.
  • Mutating schedule state inside random UI callbacks.
  • Skipping script/locale tests for CJK-heavy datasets.

Verification

  • Failed sync does not lose progress.
  • Due queue is stable for the same timestamp input.
  • Grade distribution shifts intervals as expected in tests.

Get New Tutorials by Email

No spam. Just clear, practical breakdowns you can apply right away.

Enjoy this tutorial?

Get new practical tech tutorials in your inbox.