System design
Design a Chat App (WhatsApp Web)
Difficulty: 🔴 Hard · Est. time:
1h· Tags:#realtime#websocket#offline#optimistic-ui
Asked at: Meta, Slack, Discord, Uber · Related: News Feed · Google Docs · Interview Patterns
1. The Question
Design the frontend for a real-time chat app (WhatsApp Web / Messenger): conversation list, message thread, send/receive in real time, delivery/read receipts, typing indicators, and offline support.
2. Requirements
Functional
Non-functional
3. High-Level Design
┌─────────────┐ WebSocket (bidirectional) ┌──────────┐
│ Chat Core │ ◀─────────────────────────▶ │ Gateway │
│ - socket │ messages, receipts, └──────────┘
│ - outbox │ typing, presence
│ - stores │
└──────┬──────┘
│ selectors
▼
[Conversation list] [Message thread (virtualized)] [Composer]
- Transport: WebSocket for bidirectional, low-latency messaging. Fall back to SSE/long-polling if WS is blocked.
- Local stores: normalized
messages+conversations; an outbox for pending sends. - Persistence: IndexedDB for offline history + queued messages.
4. Deep Dives & Trade-offs
Transport choice → WebSocket (server can push; low overhead). SSE is server→client only (no send channel); long-polling is a fallback. Discuss reconnection with exponential backoff + jitter and resubscribe-on-reconnect.
Optimistic send + reconciliation → render the message immediately with a temp client id and "sending" state. On server ack, replace temp id with the real id and mark "sent". On failure, show retry. This is why you key messages by a client-generated id, not the server id.
Ordering & dedupe → messages can arrive out of order or twice (reconnect replays). Order by server timestamp/sequence; dedupe by id. Detect gaps (missing sequence) and backfill via history fetch.
Offline → queue sends in the outbox (IndexedDB); on navigator.onLine/reconnect, flush in order. Show per-message state (queued → sent → delivered → read).
Receipts & typing → separate lightweight events. Typing indicators are throttled and ephemeral (don't persist). Read receipts update on viewport visibility (IntersectionObserver).
Long threads → virtualize the message list; load history in pages scrolling up while preserving scroll position (anchor to the first visible message). Evict far-offscreen messages from the DOM.
Consistency → treat the server as source of truth; local optimistic state is provisional until acked.
5. What Interviewers Probe
- WebSocket vs SSE vs polling — when each?
- How do you guarantee ordering and no duplicates across reconnects?
- Optimistic UI: temp ids, ack reconciliation, failure/retry.
- Offline queue + sync strategy.
- Reconnection/backoff and resubscribe.
- Virtualizing a thread while paginating history upward.
- Read receipts via visibility.
6. Curated Resources
- MDN: WebSocket API ⭐ — the transport
- Ably: WebSockets vs SSE vs long-polling — transport trade-offs
- web.dev: offline cookbook ⭐ — offline/sync strategies
- MDN: IndexedDB — offline persistence