Design an offline-first reader app: content model, sync, storage budget, prefetching, reading position and search.

26.1.3 · L5 · Interview

Predict first

A reader marks downloaded articles and caches thumbnails. Storage is nearly full. What may the app evict automatically?

// A: the downloaded articles, oldest first
// B: cached items (thumbnails, previews) only; downloads are pinned until the user removes them
// C: everything, including reading positions
  • A
  • B
  • C

Mental model. An offline reader is a library card with a suitcase: borrow what you will need before the trip, keep your place in each book, and let the library reconcile your bookmarks when you return.

Requirements. Browse a catalogue of articles or books, read with no connection, keep reading position and highlights across devices, and download for offline on request. Invariants: a downloaded item is readable without the network, storage use stays within a budget the user controls, and position and notes are never lost. Content model: structured content (blocks, not raw HTML) is easier to render accessibly, theme and search than web views; store it with an id and a version so updates are detectable. Source of truth: a local database for catalogue metadata, reading state and annotations, and files for bulk content and images (Q331, Q339). Sync is incremental: ask for changes since a cursor, use conditional requests (ETag, Q319) for content, and treat reading position and highlights as small user data synced both ways with a simple conflict rule (last writer wins for position, merge for highlights, tombstones for deletions, Q340). Storage budget: distinguish downloaded items (pinned by the user, never evicted automatically) from cached items (evictable, Q338); show usage, let the user remove downloads, and size the image cache and downsample images (Q327).

Prefetch and background. Refresh the catalogue and pre-download the next chapters or subscribed issues opportunistically: on Wi-Fi and charging using a background processing task (Q224), via background URLSession downloads so the system schedules them (Q314), and respect Low Power Mode and the user’s cellular settings. Background refresh is not guaranteed, so the app must work fully when it never ran. Search: a local full-text index (SQLite FTS) over downloaded content, plus Core Spotlight for system search (Q384). Degradation: offline shows a clear state and only what is available, a failed download resumes, a corrupt file is detected (checksum) and re-fetched, and an old app version reads new content gracefully (Q488). Accessibility and localisation: dynamic type reflow and text-to-speech, right-to-left languages and per-language content. Metrics: time to first readable content, offline-open success rate, download success and storage per user, sync lag. The trade-offs to name: eager download uses data and storage but gives reliability; on-demand is cheaper but fails offline; the user-facing “download” control is how you make that choice theirs.

An offline promise needs pinned content; only genuinely recomputable caches are evictable.

Common traps

  • Treating the cache as the download. A cache can be evicted at any time; offline promises need pinned content.
  • Relying on background refresh happening. The system decides; the app must be correct without it.

They might follow up with

  • What do you sync both ways? Small user data: reading position, highlights, bookmarks, with a conflict rule.
  • How do you keep storage bounded? A user-visible budget, evictable caches versus pinned downloads, and clear removal controls.

Related questions

  • 26.1.1 How do you approach a mobile system design question: what do you clarify, in what order do you design, and…
  • 26.1.2 Design a messaging client: transport, ordering, delivery states, offline sending, media, notifications and…
  • 26.1.4 Design a shopping app with cart and checkout: catalogue, cart merging, price authority, payment, idempotency…
  • 13.2.2 How do you use SQLite safely and quickly: bound parameters, indexes, and schema migrations?
  • 13.5.2 How do you design a cache: memory and disk layers, TTL, eviction, and what to key it by?
  • 13.7.1 How would you design multi-device sync: identity, change tracking, conflict resolution, deletions and…
  • 6.5.3 How does BGTaskScheduler work, and why does your task not run when you ask?
  • 17.5.2 How do Core Spotlight, NSUserActivity, Handoff and universal links make an app’s content discoverable and…

← Design a messaging client: transport, ordering, delivery states…   Design a shopping app with cart and checkout: catalogue, cart… →