Design an Offline-First Mobile App: Sync, Conflicts, and CRDTs

Updated · techinterview.org

“Design a mobile app that works fully offline” is a common system design prompt. Real-world examples: notes apps, todo apps, journaling, drawing tools, mobile email. The interview is testing whether you understand the implications of treating the device as the source of truth and reconciling with the cloud later.

Functional requirements

  • Read and write all data while offline — every read hits local storage and every write commits locally first, so the UI never blocks on the network, even in airplane mode. Interviewers probe whether you understand that the local database, not the server, is the primary read path.
  • Sync to cloud when network returns — queue local changes and flush them in the background once connectivity comes back. The user should never wait on a spinner for sync to finish.
  • Resolve conflicts when the same record was edited on two devices — decide up front whether the last edit wins, the fields merge, or the user is asked to choose. State the rule per field so the behavior is predictable and testable.
  • Multi-device support — same user on phone and tablet, each keeping its own local copy and syncing independently through the server. The case to reason about out loud is both devices going offline, diverging, then reconnecting.
  • Eventually consistent across devices — you are not promising real-time consistency, only that after enough sync cycles every device converges to the same state. That convergence guarantee is exactly what LWW and CRDTs are chosen to provide.

Storage choices

SQLite is the default. Mature, fast, ubiquitous. Pair with a thin ORM (Room on Android, GRDB on iOS).

Realm is object-oriented with sync built in. The tradeoff is vendor lock-in.

Document store options (RxDB, Couchbase Lite) are schemaless and sync-friendly.

Sync strategies

Last-writer-wins

Simplest. Each record has a updatedAt. On sync, the device with the larger timestamp wins. Pitfall: clock skew. Use a hybrid logical clock (HLC) to combine wall time with logical sequence.

Operational transformation (OT)

Used by Google Docs. Operations are transformed against concurrent edits to converge. Complex; usually overkill for mobile-only apps.

CRDTs

Conflict-free replicated data types. Mathematical guarantee of convergence under any merge order. Yjs and Automerge are the popular libraries. Cost: more storage overhead per record (vector clocks, op logs).

For a notes app: per-field LWW with HLC timestamps. For collaborative real-time editing: CRDT (Yjs). For a todo app where order matters: fractional indexing for ordering, LWW for fields.

Tombstones and deletions

Soft deletes only. A deleted record becomes a tombstone with a deletion timestamp. Tombstones are garbage-collected after a TTL longer than the longest possible offline period (~30 days).

Sync protocol

  • Client tracks a lastSyncedAt per table
  • On sync trigger (foreground, push, periodic), client sends “give me changes since X”
  • Server returns batch of records updated after X
  • Client applies merge logic per record
  • Client uploads its own pending changes (those with dirty=1 flag)
  • Server merges and returns canonical versions
  • Client clears dirty flags

Battery and network

  • Sync triggers: app foreground, push notification “data available”, periodic background fetch. Modern mobile OSes throttle background fetch aggressively, so treat foreground and push as your reliable triggers and periodic fetch as best-effort.
  • Compress payloads (gzip) — sync bodies are mostly text and typically shrink by more than half, which cuts both bandwidth and the radio-on time that drains the battery.
  • Diff-only sync, never re-upload entire DB — send only records changed since lastSyncedAt, so payload size scales with edits made, not total data stored.

Frequently Asked Questions

Should I store data in JSON or relational?

For interview answers, relational with a proper schema. Easier to reason about indices, queries, and migrations. Use JSON columns for genuinely flexible blobs.

How do I handle schema migrations?

Bundled migration scripts that run on app launch. Each migration is idempotent and versioned. Always test on real devices with old data.

What about end-to-end encryption?

Encrypt at rest on the device (SQLCipher) and in transit (TLS). For E2EE between devices, exchange keys via a trusted setup (QR code or secret derivation from password).

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

newsletter

What's actually being asked right now

Interview patterns & comp trends, straight to your inbox.

No spam. Unsubscribe anytime.

1972 Soviet postage stamp commemorating the Mars 2 probe

worth a read

Mars For The Rest of Us — a weekly-or-more deep dive on the technical side of Mars exploration: rocket propulsion, microbiology, mission architecture, and everything in between. Written by Maciej Ceglowski.

Read it on Substack
Scroll to Top