00. Design WhatsApp / Messenger (Chat)¶
~20 min read · Level: advanced · Part 1 of 4 (Overview → HLD → LLD → Q&A)
Problem¶
A chat system delivers messages between users in near real time, reliably, in order, whether the recipient is online or offline, and shows delivery/read receipts. The hard parts are not the UI — they are holding hundreds of millions of persistent connections, guaranteeing each message is delivered exactly once and in order, and queuing for offline recipients so nothing is lost when a phone is off.
Thread one scenario: Alice sends "running late" to Bob, whose phone is offline; ten minutes later Bob reconnects and must receive it exactly once, in order relative to Alice's other messages, and Alice must see the receipt flip from sent → delivered → read. Get that one message right under connection churn and retries, and the system works.
Functional requirements¶
- 1:1 messaging in real time.
- Group messaging (up to, say, 256 members).
- Offline delivery: messages queue and deliver when the recipient reconnects.
- Delivery and read receipts (sent / delivered / read).
- Online/last-seen presence.
De-scoped: voice/video calls (a separate real-time-media problem), media file transfer internals (reuse the blob+CDN pattern), and end-to-end encryption key exchange details (noted where it constrains the design, not fully specified).
Non-functional requirements¶
The dominant constraint is reliable, ordered, exactly-once delivery over unreliable mobile connections at massive connection scale.
- Latency: online-to-online delivery < 500 ms end to end.
- Delivery guarantee: no lost messages; no duplicates shown to the user.
- Ordering: messages within a conversation appear in send order.
- Availability: the messaging path must stay up; presence can degrade.
- Durability: undelivered messages persist until delivered (or an expiry for extreme cases).
Scale estimation¶
Assume 2B users, 500M concurrent connections at peak, and 100B messages/day. That's 100B / 86,400 ≈ 1.16M messages/second average, several million/second at peak. Messages are tiny (~100 bytes of text + metadata), so throughput, not storage, is the pressure: 100B/day × ~200 B ≈ 20 TB/day of message data, most of it deletable once delivered.
The connection count is the defining number: 500M concurrent long-lived connections can't live on one box. At ~100k connections per gateway node, that's ~5,000 connection nodes, each holding sockets and forwarding messages — a large but horizontally-scaled fleet. Bob's offline message is one row in a per-user inbox queue; the interesting cost is routing Alice's message to whichever gateway node currently holds Bob's socket, and persisting it when no node does.
API sketch¶
WS /connect → authenticated persistent connection
⇅ SEND {to, conv_id, client_msg_id, text}
⇅ ACK {server_msg_id, status} -- server persisted it
⇅ DELIVER{server_msg_id, from, text} -- pushed to recipient
⇅ RECEIPT{server_msg_id, state: delivered|read}
GET /messages?conv_id=…&since=… → history sync on reconnect
Solutioning¶
The backbone is a connection layer: a fleet of gateway nodes each holding ~100k WebSocket connections, plus a session registry mapping user_id → gateway node so any message can be routed to the node holding the recipient's socket. When Alice sends, her gateway persists the message, looks up Bob's session, and forwards it to Bob's gateway, which pushes it down his socket. Bob offline means no session entry — so the message waits in his inbox queue until he reconnects and drains it.
The defining problem is exactly-once, ordered delivery over a lossy channel, and the reframing is important: you cannot get exactly-once on the wire, so you get at-least-once delivery plus client-side dedup. Every message carries a client_msg_id (dedup at the server) and gets a monotonic server_msg_id per conversation (ordering). The server persists before acking, so a lost ack causes a retransmit, not a lost message; the client dedups by id so a retransmit isn't shown twice. Ordering is per-conversation: the server assigns a sequence number per conversation, and the client renders by that sequence, so Alice's "running late" slots correctly among her other messages even if it arrives after a later-sent one.
Bob's offline case falls out naturally: persist first, deliver best-effort. The message lands in Bob's durable inbox queue keyed by conversation sequence. On reconnect, Bob's client sends its last-seen sequence and the server streams everything after it, in order. Receipts are just messages in the other direction: Bob's client ACKs delivery when it stores the message and sends a read receipt when Bob opens the chat; Alice's gateway routes those back to her.
Groups are 1:N fan-out of the same mechanism: the server expands a group message to each member's inbox/session, dedup and ordering applied per member. The tradeoff for large groups is fan-out cost, capped by the member limit. The files below detail the connection layer, the delivery protocol, and ordering.