Building Hashcards Mobile App
The Technical Architecture
This post covers the technical decisions behind Hashcards. For the story of why we built it, read The Hashcards Journey.
The FSRS Algorithm
Most flashcard apps still use SM-2, an algorithm from the late 80s. It works, but it’s been surpassed. FSRS (Free Spaced Repetition Scheduler) is the result of years of research and optimization using real user data.
We use ts-fsrs, the official TypeScript implementation of FSRS. Unlike SM-2’s fixed intervals, FSRS tracks three memory variables: difficulty, stability, and retrievability. When you grade a card, the algorithm calculates exactly when you’ll need to see it again to maintain optimal recall.
Cards appear when you need to review them. Not sooner (wasting time), not later (after you’ve forgotten).
Content-Addressable Cards
Each card gets a unique hash based on its content using BLAKE3. Edit a card and the content changes, so the hash changes. But we preserve your FSRS progress: stability, difficulty, interval, lapses. Your learning history stays intact even when you fix a typo or rephrase a question.
This design comes from a command-line tool with web interface called hashcards. Cards are plain text:
Q: What is the capital of France?
A: Paris
C: The [mitochondria] is the powerhouse of the cell.
Simple. Predictable. No duplicates.
Real-time Sync with InstantDB
Traditional database patterns are painful for real-time apps. You write fetch calls, handle loading states, manage cache invalidation, deal with optimistic updates, worry about offline support. It’s a lot of code for basic functionality.
InstantDB flipped this. It’s a frontend-first database where you describe what you want, and it handles the rest. Optimistic updates out of the box. Offline mode built in. Real-time sync across devices.
The guest account model came naturally from this. You can use the app without signing up. Your data lives locally. When you’re ready to sync across devices, just add your email. Same data, now with backup and cross-device access.
Less code. Fewer bugs. More focus on the actual product.
Formula Cards and KaTeX
If you’re studying math, physics, or chemistry, you need proper formula support.
We built a custom formula keyboard that lets you type LaTeX naturally. Fractions, integrals, Greek letters, all accessible with a tap.
Integrating KaTeX into React Native was its own adventure. We built a LaTeX parser that detects inline and block math expressions, and a symbol registry that organizes everything from basic operators to Greek letters to calculus notation to chemistry elements. The registry powers the keyboard categories: numbers, variables, operators, structures, calculus, trigonometry, geometry, sets and logic, brackets, and chemistry. Each symbol maps to its LaTeX representation. WebViews handle the rendering, with careful height calculation to make sure the preview matches the final card. The result: formula cards that look right and are easy to create.
Handwritten Cards
Here’s where we made a deliberate choice to diverge between platforms.
On iOS, PencilKit gives you native Apple Pencil support. The drawing experience is what iPad users expect: pressure sensitivity, palm rejection, all of it working perfectly because it’s the system framework.
On Android, we went with Skia. Different implementation, same goal: let people sketch diagrams, write equations by hand, draw whatever helps them learn.
We could have forced one solution on both platforms. We chose native excellence over cross-platform convenience.
In-App Purchases with RevenueCat
Payments were the most frustrating part of this project.
We built everything with Stripe. Then Apple rejected the app. Their policy: consumable digital content must go through their payment system. AI credits count as consumable digital content.
We already had a working payment system. Users trust Stripe. But Apple mandates their system, takes 30% of every transaction, and leaves developers no choice.
We rebuilt the entire payment flow. Weeks of additional work for something that already worked.
RevenueCat abstracts App Store and Play Store purchases behind a unified API. The architecture:
-
Client-side:
react-native-purchaseshandles the native IAP flow. Users are identified by their InstantDB user ID, linking purchases to their account. -
Webhook processing: When a purchase completes, RevenueCat sends a webhook to our Cloudflare Worker. We verify the authorization, check for duplicate transactions (idempotency), and credit cards to the user’s billing profile.
-
Consumable products: Four credit packs—Starter (50 cards), Standard (200), Pro (500), and Max (2000)—each mapped to a product ID. Purchases credit cards to the user’s billing profile in InstantDB.
RevenueCat handles receipt validation, entitlement management, and provides a dashboard for tracking revenue across both stores.
Daily Reminders
We use OneSignal for daily reminders. A worker runs every 15 minutes, checks who opted in, and sends notifications at the time they chose. Opt-in, easy to disable. The app reminds you once and gets out of your way.
Knowledge that stays. Build cards. Then remember them.