Back to BlogEngineering

Building Offline-First Mobile Applications

Sync engines, conflict resolution, local-first storage, and the patterns that make mobile apps work without an internet connection.

Priya Patel Sep 18, 2025 10 min read
Offline-First Mobile Sync Local Storage
Building Offline-First Mobile Applications

Mobile users aren't always connected. Elevators, subway tunnels, rural areas, airplane mode, spotty WiFi — offline scenarios are not edge cases, they're everyday reality. Offline-first design means your app works fully without a connection and syncs when connectivity returns. This is fundamentally different from 'graceful degradation' — it's a core architecture decision that affects every layer of your stack.

Mobile app usage in various environments
Offline-first isn't about handling failures — it's about designing for the reality of mobile connectivity

Local-First Architecture

In an offline-first app, the local database is the source of truth, not the server. Reads always go to the local database (instant, no loading states). Writes go to the local database first, then sync to the server in the background. This inverts the traditional client-server model — the server is the sync partner, not the authority.

  • Local storage: SQLite (via Expo SQLite or WatermelonDB) for structured data. AsyncStorage for key-value preferences.
  • Write-ahead log: Every local mutation is recorded in a sync queue before being applied to the local database.
  • Background sync: When connectivity returns, the sync queue is replayed to the server. Use exponential backoff for failed syncs.
  • Optimistic UI: Show the result of the user's action immediately based on the local write. If the server rejects it, roll back and notify the user.

Conflict Resolution

When two users edit the same data offline and both sync, you have a conflict. There's no universal solution — the right strategy depends on your domain. Last-write-wins is simplest but lossy. Operational transforms (OT) and CRDTs (Conflict-free Replicated Data Types) can merge concurrent changes automatically for collaborative editing. For most business apps, we use field-level last-write-wins with conflict detection and user-facing resolution.

sync/conflict-resolver.ts
interface SyncConflict<T> {
  localVersion: T;
  serverVersion: T;
  baseVersion: T;  // Common ancestor
}

function resolveConflict<T extends Record<string, unknown>>(
  conflict: SyncConflict<T>
): T {
  const resolved = { ...conflict.serverVersion };

  // Field-level merge: if only one side changed a field, take that change
  for (const key of Object.keys(conflict.baseVersion)) {
    const localChanged = conflict.localVersion[key] !== conflict.baseVersion[key];
    const serverChanged = conflict.serverVersion[key] !== conflict.baseVersion[key];

    if (localChanged && !serverChanged) {
      // Only local changed this field — take local
      resolved[key] = conflict.localVersion[key];
    } else if (localChanged && serverChanged) {
      // Both changed — flag for manual resolution
      throw new ConflictError(key, conflict.localVersion[key], conflict.serverVersion[key]);
    }
    // If only server changed or neither changed, keep server version (default)
  }

  return resolved as T;
}

Testing Offline Scenarios

Offline behavior must be tested explicitly. Simulate network conditions in your test suite: no connectivity, intermittent connectivity, slow connections, and connectivity restoration during mid-operation. Use airplane mode on physical devices for manual testing — emulator network simulation doesn't perfectly replicate real-world conditions.

Design your UI for the offline state first, then add the connected state as an enhancement. If your app works beautifully offline, it'll work even better online.

Offline-first is harder to build than online-only, but the user experience is dramatically better. Instant responses, no loading spinners, no 'connection lost' error screens. For mobile apps where connectivity is unreliable, offline-first isn't a feature — it's the foundation of a good user experience.

P

Priya Patel

Senior Backend Engineer