DEV Community

Cover image for How WhatsApp Works Without Internet: Offline Messaging and Sync Explained
Janmejai Singh
Janmejai Singh

Posted on

How WhatsApp Works Without Internet: Offline Messaging and Sync Explained

You're on a train, signal drops to zero. You type a message and hit send. A single grey tick appears.

Then — the moment you exit the tunnel — two ticks appear.

How did that work? You were offline.

This isn't magic. It's offline-first architecture, and it's one of the most practical ideas in mobile engineering. Let's break it down.


The Scenario: Sending a Message in Airplane Mode

Imagine you toggle airplane mode and type:

"Just took off. Landing in 3 hours!"

WhatsApp doesn't show an error. It doesn't freeze. You see a message bubble with one grey tick.

Here's what actually happened:

  1. The app saved your message to a local database on your phone
  2. It added the message to an outgoing queue
  3. It rendered the message immediately in the UI — no waiting
  4. It set the internal status to PENDING

Your message never touched a server. The app made a promise to deliver it when connectivity returns. That promise — that optimistic local-first action — is the heart of offline-first design.


Local Storage and Message Persistence

Every message is first written to a local SQLite database on the device, before anything goes to the cloud.

WhatsApp uses SQLite. Android apps use Room. iOS apps use Core Data. In React Native, you'd reach for expo-sqlite or @op-engineering/op-sqlite.

A simplified message record looks like this:

CREATE TABLE messages (
  id              TEXT PRIMARY KEY,  -- Generated on device, not server
  conversation_id TEXT NOT NULL,
  content         TEXT NOT NULL,
  created_at      INTEGER NOT NULL,  -- Device timestamp
  status          TEXT DEFAULT 'PENDING',
  media_uri       TEXT,              -- Local file path for media
  server_ack_id   TEXT               -- Set after server confirms receipt
);
Enter fullscreen mode Exit fullscreen mode

Two things worth noting:

  • The ID is generated on the device, so WhatsApp can display the message before the server even knows it exists.
  • Status starts as PENDING — honest, explicit, and essential for the queue system.

The UI reads from local storage. It never waits for the network.


The Message Queue

When a message is saved locally, it also enters an outgoing queue — a persistent list of things the app needs to send when it can.

┌──────────────────────────────────────┐
│         OUTGOING MESSAGE QUEUE       │
│  [msg_001] "Just took off..." PENDING│
│  [msg_002] "See you soon!" PENDING   │
│  [msg_003] photo.jpg       PENDING   │
└──────────────────────────────────────┘
         ↕  No internet — held on device
Enter fullscreen mode Exit fullscreen mode

This queue is written to disk, not held in RAM. If the app crashes, the queue survives. When internet returns, the app processes messages in order, retrying failures with exponential backoff (1s → 2s → 4s → ...) to handle flaky connections gracefully.


Syncing When Connectivity Returns

The moment internet returns, the app fires off four steps almost simultaneously:

Internet returns
      │
      ▼
1. Flush outgoing queue  →  Send all pending messages to server
      │
      ▼
2. Pull missed messages  →  Fetch messages sent to YOU while offline
      │
      ▼
3. Reconcile local DB    →  Merge server state with local state
      │
      ▼
4. Update the UI         →  Ticks change, new messages appear
Enter fullscreen mode Exit fullscreen mode

Step 1 — Each queued message is sent to WhatsApp's servers. The server acknowledges receipt and the status upgrades from PENDING to SENT.

Step 2 — People may have messaged you while you were offline. The app requests everything from the server since the last sync timestamp.

Step 3 — Local DB is updated with what came from the server.

Step 4 — Because the UI observes the local DB reactively, everything updates automatically. No manual refresh needed.


Delivery States: Sent, Delivered, Read

The tick system maps directly to a message state machine:

[PENDING] → [SENT] → [DELIVERED] → [READ]
  clock      1 grey    2 grey        2 blue
              tick      ticks         ticks
     └──────→ [FAILED] (retries exhausted)
Enter fullscreen mode Exit fullscreen mode
State What it means
Pending Saved locally, not yet on server
Sent Server received it
Delivered Recipient's device has it
Read Recipient opened the conversation
Failed Couldn't deliver after all retries

State transitions are triggered by acknowledgements (acks) from the server or recipient's device via a persistent connection.

Why separate "Sent" from "Delivered"? Because the server having your message ≠ the recipient's phone having it. They could be offline too.


Handling Media While Offline

Text messages are tiny. Photos and videos are not. WhatsApp handles this with a decoupled upload strategy:

  1. Capture & save locally — photo is stored on device
  2. Queue the message with a local file reference (local://img_001.jpg)
  3. Upload media in background when internet returns
  4. Server returns a CDN URL — message record updates from local:// to https://mmg.whatsapp.net/...
  5. Recipient downloads on demand — they get the URL and fetch media lazily

This is why you sometimes see a blurred thumbnail or "Waiting for media" — the text message arrived, but media hasn't been downloaded yet.


Conflict Resolution and Message Ordering

What happens when messages arrive out of order after going offline?

The problem: Device clocks are unreliable. Your phone might be 30 seconds off, or in the wrong timezone. If WhatsApp trusted device timestamps alone, messages could appear out of order.

The solution:

  • Server-assigned timestamps — when a message reaches the server, it gets a canonical timestamp used for ordering
  • Sequence numbers — monotonically increasing numbers break ties between messages with identical timestamps
  • Last-write-wins for status — message status can only move forward (SENT → DELIVERED, never backwards)

This is a simplified form of eventual consistency:

The system doesn't guarantee every device sees the same state at the same instant. But given time and connectivity, all devices converge on the same state.

For messaging, this is an acceptable tradeoff. A message appearing 200ms out of order is fine. A message that never appears is not.


Why Offline-First Matters

Traditional approach:

User action → Network request → Update UI (only if network works)
Enter fullscreen mode Exit fullscreen mode

Offline-first approach:

User action → Update local DB → Update UI → Background sync to server
Enter fullscreen mode Exit fullscreen mode

The difference in feel is enormous.

Traditional (online-first) Offline-first
Broken without internet Works fully offline
User waits on every action Instant UI feedback
Lost actions on failure Queue retries automatically
Spinner on every tap Smooth always
Terrible on slow networks Great on any network

The tradeoffs are real though:

  • Conflict resolution becomes your responsibility
  • Local data needs encryption (WhatsApp uses SQLCipher)
  • Testing network edge cases is harder
  • Storage management (when do you purge old queued messages?)

The Full Lifecycle in One Diagram

User types and hits send
        ↓
App generates local ID
        ↓
Saved to SQLite (PENDING)
        ↓
Added to outgoing queue
        ↓
UI renders immediately (optimistic update)
        ↓
─── [Internet returns] ───────────────────
        ↓
Queue flushed → sent to server
        ↓
Server acks → status: SENT (1 grey tick ✓)
        ↓
Recipient's device comes online → delivered
        ↓
Delivery ack → status: DELIVERED (2 grey ✓✓)
        ↓
Recipient opens chat → read receipt sent
        ↓
Read ack → status: READ (2 blue ✓✓) ✅
Enter fullscreen mode Exit fullscreen mode

Building This in React Native

Here's the toolkit you'd reach for:

Need Library
Local DB expo-sqlite or @op-engineering/op-sqlite
Fast key-value (queue) react-native-mmkv
Network state @react-native-community/netinfo
Background sync expo-task-manager + background fetch
Real-time acks socket.io-client or Firebase RTDB

The pattern is always: local first → background sync → UI driven by local state.


Key Takeaways

  • WhatsApp never waits for the network — it writes locally and syncs later
  • The outgoing queue persists to disk and survives crashes
  • Delivery states (PENDING → SENT → DELIVERED → READ) are driven by server and device acks
  • Media uploads are decoupled from text — text queues fast, media uploads in background
  • Server-side timestamps and sequence numbers resolve ordering conflicts
  • Offline-first trades implementation complexity for much better UX

What to Explore Next

  • WatermelonDB — a React Native DB built specifically for offline-first at scale
  • CRDTs — the math behind conflict-free sync
  • Firebase Firestore offline persistence — a managed offline-first solution
  • XMPP / MQTT — the protocols behind real-time messaging

Next time you see those grey ticks turn blue on a plane with spotty Wi-Fi, you'll know exactly what just happened. 🛫

Found this useful? Drop a ❤️ and share it with your dev cohort!

Top comments (0)