intermediate

Local-first apps

Model offline writes, sync, conflicts, migrations, and merge semantics when SQLite backs local-first UX.

Local-first apps treat the on-device SQLite database as the source of UX truth: reads and writes work offline, then sync reconciles with a server or peers.

Design concerns:

  • **Offline writes**: queue mutations with client-generated IDs (UUID) until sync succeeds
  • **Conflict resolution**: last-write-wins, version vectors, CRDTs, or domain-specific merge
  • **Migrations**: version schema per app release; handle partial upgrades on user devices
  • **Sync protocol**: push/pull deltas, tombstones for deletes, backoff and idempotency
					CREATE TABLE notes (
  id TEXT PRIMARY KEY,
  body TEXT NOT NULL,
  updated_at INTEGER NOT NULL,
  deleted INTEGER DEFAULT 0,
  sync_version INTEGER DEFAULT 0
);

-- Pull changes since cursor
SELECT * FROM notes WHERE sync_version > ? OR updated_at > ?;
				

SQLite enables instant UI; sync layer owns consistency boundaries. CRDT libraries or ElectricSQL/LiteFS-style tools change operational shape but not the core trade-off.

On interviews: walk through offline create → reconnect → conflict; explain tombstones and idempotent sync; separate local UX consistency from server authority.

Common pitfalls: server auto-increment IDs breaking offline creates; syncing entire tables; no delete propagation; conflict policy undefined until users complain.

The trade-off is responsive offline UX versus sync complexity, conflict handling, and testing burden across device versions.

Checklist:

  • Client IDs for offline creates.
  • Explicit conflict and delete (tombstone) policy.
  • Versioned migrations on device.
  • Idempotent sync with cursors or change feeds.