Question 4
Offline support and syncing
Your app needs to work offline and sync with a backend. How do you design your persistence layer to handle conflicts and ensure consistency?
Follow-ups
- How do you represent pending writes?
Answer outline
Make the local database the single source of truth for the UI. Users read and write local state immediately, and a sync layer runs in the background to reconcile with the server whenever it can.
Represent pending mutations with an outbox, a persisted queue of operations that survives crashes and can be replayed. Each record carries the operation type, entity ID, payload, and retry state, so backoff and recovery are deterministic.
Define a conflict policy per entity type. Start simple: server wins for most fields, with merge rules for anything user-editable. Use an updatedAt timestamp or an ETag (entity tag) to detect when a conflict has happened.
Principles
- Make every sync operation idempotent, with stable operation IDs so applying a mutation twice can't double its effect.
- An outbox keeps offline writes crash-safe, because a queued mutation survives a restart and replays later.
- Keep sync state separate from domain data, and track pending, failed, and synced status explicitly.
- Decide the conflict policy up front, rather than letting the last write win by default.
An outbox record captures everything needed to replay or retry a mutation after a crash:
struct PendingMutation {
let opId: UUID
let entityId: String
let type: MutationType // create, update, or delete
let payload: Data
var attemptCount: Int
}
Follow-up angles
- Show sync errors clearly in the UI, with a badge or a retry control, so failures are visible and recoverable.



