← All topics/Data persistence

Practical interview questions

Scenario-style prompts with sample answer outlines. Focus is on how you would design and reason in real codebases.

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

Use the local database as the source of truth for UI. Users interact with local state immediately; a sync layer runs opportunistically in the background to reconcile with the server.

Represent pending mutations with an outbox — a persisted queue of operations that survive crashes and can be replayed. Each record carries the operation type, entity ID, payload, and retry state, making backoff and recovery deterministic.

Define a conflict policy per entity type. Start simple: server wins for most fields, with merge rules for anything user-editable. Use updatedAt timestamps or ETags to detect when a conflict has occurred.

Principles

  • Make sync operations idempotent — applying the same mutation twice must not double the effect; use stable operation IDs to deduplicate.
  • The outbox pattern — a persisted queue of mutations — is what makes offline writes crash-safe and retryable.
  • Separate sync state from domain data — track pending, failed, and synced status explicitly.
  • Define a conflict policy upfront; don’t rely on whichever write arrives last.

An outbox record captures everything needed to replay or retry a mutation after a crash:

Outbox record sketch
struct PendingMutation {
    let opId: UUID
    let entityId: String
    let type: MutationType   // create/update/delete
    let payload: Data
    var attemptCount: Int
}

Follow-up angles

  • Show clear UI for sync errors (badge/retry) so failures are visible and recoverable.