System Design Case Study

How does a multi-device messaging app keep messages perfectly synced across all devices?

🎯 Design a multi-device sync system that keeps messages, read status, and edits consistent across phone/tablet/desktop
Concepts Involved

How does a multi-device messaging app keep messages, read status, and edits perfectly synced across phone/tablet/desktop, resolving conflicts when edits happen simultaneously on different devices?

Scope: Multi-device synchronization of messages, read markers, and edits · not E2E encryption key management, not media sync. The hard question: how do you guarantee every device converges to the same state when edits happen simultaneously on different devices with different network conditions✗
3-5
devices per user avg
phone + tablet + desktop
<1s
sync latency P99
action on one → visible on all
pts/qts
sequence numbers
per-user update counter
LWW
conflict resolution
last-write-wins + vector clock

Functional Requirements

What the system must do · server-authoritative sync with conflict resolution

Must Have (Core)

1. All devices see the same messages in the same order
2. Read status syncs across devices · read on phone → marked read on desktop
3. Message edits/deletes propagate to all devices within 1 second
4. Offline catch-up · device that was offline gets all missed updates on reconnect
5. Conflict resolution · simultaneous edits on different devices converge to same state
6. Efficient sync · only transfer delta (missed updates), not full state

Out of Scope (this design)

E2E encryption key distribution across devices (separate protocol)
Media file sync (lazy download, separate blob storage)
Device authorization (QR code login, session management)
Typing indicators (ephemeral, not synced · see Typing Indicators)
Contact sync (different data model, different sync frequency)

Non-Functional Requirements

Quality constraints for multi-device synchronization at global scale

PropertyTargetWhy It Matters / Design Impact
Sync latency<1s for real-time push to all devicesAction on one device must appear on others near-instantly to feel like one unified inbox.
Catch-up latency<3s for getDifference on reconnectDevice coming online must sync quickly · users won't wait for stale state to resolve.
ConsistencyStrong per-user (all devices converge to same state)Divergent state across devices is the worst UX · messages appearing on one but not another.
Throughput100B+ updates/day across all usersEvery message, read receipt, edit, and delete is an update that must reach all devices.
Retention7-day update log per userOlder than 7 days = force full re-sync. Keeps storage bounded while covering typical offline gaps.
Conflict resolutionLast-write-wins with server timestampSimple, deterministic resolution. Server is authoritative · no client-side merge logic needed.
Multi-deviceSupport 3-5 concurrent devices per userPhone + tablet + desktop is the common case. Each maintains independent cursor.

Scale Estimation

Deriving infrastructure requirements from device count and update volume

StepCalculationResult
1700M MAU · 3 devices avg2.1B device sessions
2100B msgs/day → each msg = 1 update · 3 devices300B update deliveries/day
3300B · 86400~3.5M update pushes/sec
4Update log storage: 700M users · 7 days · 50 updates/day · 200B~49TB
5getDifference calls: 700M users · 5 reconnects/day · 86400~40K calls/sec
6Conflict rate: simultaneous edit on 2 devices<0.01% of edits (rare)

Architecture Overview

Server as source of truth · each device maintains a pts (update sequence) and uses getDifference to catch up

REAL-TIME SYNC · User sends message from Phone → Desktop sees it instantly ➔ Phone sends message local pts = 47 Server (Source of Truth) assigns pts = 48 persists update to user's log broadcasts to all user's sessions ➔ Desktop receives pts=48 update ➔ Tablet receives pts=48 update pts/qts Sequence Numbers pts (per-user): increments on every update for this user qts (per-user secret chats): separate counter for E2E chats Each device tracks its own last_pts · gap = missed updates Device pts=45, server pts=48 → getDifference returns 3 updates CATCH-UP PATH · Desktop was offline, reconnects with pts=45 ➔ Desktop reconnects, local pts=45 getDifference(pts=45) returns updates 46, 47, 48 includes messages, reads, edits Server Update Log ordered list of all user updates retained for 7 days (configurable) Desktop applies updates sequentially pts 45?46?47?48 (no gaps) Now in sync with phone and tablet CONFLICT RESOLUTION · Simultaneous edits on different devices Conflict Scenario Phone edits msg at T=100ms Desktop edits same msg at T=150ms Both arrive at server within same window Resolution: Last-Write-Wins (LWW) Server timestamps both edits Higher timestamp wins (Desktop edit at T=150) Losing edit discarded, winner broadcast to all Vector Clock (Advanced) Each device maintains {device_id: counter} Detects true conflicts vs. causal ordering For chat: LWW is sufficient (edits are rare)

Key Design Decisions

Server-authoritative model simplifies sync · unlike WhatsApp's phone-primary approach

DecisionChoiceWhyAlternative Rejected
Authority ModelServer as source of truthAll devices are equal peers. Server holds canonical state. Any device can send/edit. No "primary device" concept.Phone-primary (WhatsApp): desktop depends on phone being online. Terrible UX for multi-device.
Sync Protocolpts sequence + getDifferenceEach user has a monotonic update counter (pts). Device sends last known pts → server returns all updates since. Efficient delta sync.Full state sync: transfer entire inbox on reconnect · O(messages) instead of O(missed updates).
Conflict ResolutionLast-Write-Wins (server timestamp)Simple, deterministic. For chat edits, conflicts are rare and LWW is acceptable. Server assigns authoritative timestamp.CRDT/OT: overkill for chat messages (not collaborative editing). User-prompt: bad UX for background sync.
Update Retention7-day update log per userIf device offline >7 days, force full re-sync. Keeps update log bounded. Most devices sync within hours.Infinite retention: unbounded storage growth. 24h: too aggressive for vacation scenarios.
Real-time PushPersistent connection per deviceEach device maintains its own WebSocket/TCP connection. Server pushes updates to all active sessions simultaneously.Polling: adds latency, wastes bandwidth. Single connection: can't push to multiple devices.
Cross-reference: The sequence-based catch-up mechanism is similar to Slack's channel_seq approach, but applied per-user instead of per-channel. Both use monotonic counters for gap detection and delta sync.

Resilience & Edge Cases

Multi-device sync must handle network partitions, clock skew, and long offline periods gracefully

#Failure / Edge CaseWhat BreaksHow It's Handled
1Device offline for 8+ daysUpdate log expired · can't getDifferenceServer returns "too old" error. Client performs full re-sync: fetch recent conversations, messages paginated from server.
2Simultaneous edit on 2 devicesBoth devices think their edit is the latestServer applies LWW (last timestamp wins). Losing device receives the winning edit as a new update. UI shows final state.
3Network partition during syncDevice receives partial update batchgetDifference is idempotent · client retries with same pts. Server returns same updates. No partial application.
4Read on phone, desktop shows unreadRead status not synced yetRead marker is an update with its own pts. Propagates to all devices within 1s. Desktop clears unread badge on receiving update.
5Delete message on one deviceOther devices still show the messageDelete is an update event (pts incremented). All devices receive "delete msg_id" update and remove from local store.
Key insight: Every action (send, edit, delete, read, pin) is modeled as an update event with a pts. This unified model means the sync protocol handles all operations identically · the client just applies updates in order regardless of type.

Tech Stack & Tradeoffs

Optimized for consistent ordering and efficient delta sync

ComponentTechnologyWhy ThisWhy Not X
Update LogCassandra (per-user partition)Append-only log of updates per user. Partition key = user_id. Ordered by pts. Fast range queries for getDifference.MySQL: write bottleneck at scale. Kafka: not queryable by arbitrary offset range per user.
Sequence GeneratorRedis INCR (per-user key)Atomic increment for pts. O(1), sub-ms. Each user has independent counter · no global bottleneck.DB auto-increment: too slow for real-time. Snowflake IDs: not monotonic per user.
Real-time PushPersistent TCP/WebSocket per deviceServer pushes updates to all active sessions. Each device has independent connection. Multiplexed protocol.Polling: adds 1-5s latency. Push notifications: not real-time enough for active sessions.
Conflict ResolutionServer-side LWW (timestamp)Simple, deterministic. Server clock is authoritative. No client clock dependency.Vector clocks: more complex, marginal benefit for chat (edits are rare). CRDTs: overkill.
Session RegistryRedis (user → active sessions)Track which devices are online for push routing. TTL-based cleanup on disconnect.In-memory on gateway: not shared across gateway instances.
Cross-reference: The per-user sequence number approach mirrors Slack's per-channel channel_seq but scoped to a user across all their conversations. Both enable efficient gap detection and delta sync without transferring full state.

Interview Cheat Sheet

The 5 things an interviewer wants to hear about multi-device sync

? Server as Source of Truth (Not Phone-Primary)
Unlike WhatsApp (phone must be online), the server holds canonical state. All devices are equal peers · any can send, edit, or delete. This enables true multi-device: desktop works independently even if phone is off.
? pts Sequence + getDifference for Delta Sync
Each user has a monotonic counter (pts). Every update (message, edit, read, delete) increments pts. On reconnect, device sends last_pts → server returns only missed updates. O(missed) not O(total). Efficient even after hours offline.
? Last-Write-Wins Conflict Resolution
Simultaneous edits from different devices: server assigns authoritative timestamp, highest timestamp wins. Simple and deterministic. For chat (where edit conflicts are rare), LWW is the right tradeoff · no need for CRDTs or operational transforms.
? Persistent Connection per Device for Real-Time Push
Each device maintains its own WebSocket/TCP connection. Server pushes every update to ALL active sessions simultaneously. No polling delay. Session registry in Redis tracks which devices are online for routing.
? Unified Update Model (Everything is an Event)
Messages, edits, deletes, read markers, pins · all modeled as update events with a pts. The sync protocol doesn't care about event type. Client applies updates in order. This simplifies the protocol enormously and makes it extensible.
One-liner answer: "Server-authoritative model with per-user pts sequence numbers, getDifference API for efficient delta sync on reconnect, last-write-wins conflict resolution with server timestamps, persistent connection per device for real-time push, and a unified update model where every action is a sequenced event."