← System design

Communication

Design WhatsApp

A messenger looks like a single arrow from one phone to another. In practice the arrow passes through a server that has to find which of hundreds of machines currently holds the recipient's connection, a store that keeps the message until every one of their devices says thank you, and a receipt path that runs the whole journey in reverse. Everything is encrypted end to end, so the servers move envelopes they cannot read.
Core: connection registry + per-device inboxes67B messages sent a day220M sockets open at peakDelivered once per device, never lost

01

Scope and guarantees

02

Scope and guarantees

Functional
  • Send text and media messages one to one and in groups of up to 1,024 members.
  • Messages wait on the server for recipients who are offline and arrive when they reconnect.
  • Show three states per message: sent to server, delivered to a device, read.
  • One account can be linked to a phone plus up to four companion devices, each of which receives every message.
  • Typing indicators and an optional "last seen" time.
  • Photos, voice notes, video and documents up to 2 GB.
  • Content is end-to-end encrypted; the service never sees plaintext.
Non-Functional
  • Between two online users in the same region, median delivery under 150 ms and p99 under 500 ms.
  • Once the server acknowledges a message, it is never lost, even if a data centre fails.
  • Each device shows each message exactly once, in a consistent order within a chat.
  • Sending keeps working during partial outages; presence and typing can degrade first.
  • Battery and data use on phones must stay low, so the protocol is compact and keepalives are rare.
  • Undelivered messages are kept for 30 days, then dropped.
Clarify before designing

Ask whether the server keeps chat history or only holds messages until delivery. A store-and-forward design that deletes after delivery is far smaller and fits end-to-end encryption naturally. A server-side history, like Slack or Telegram cloud chats, turns this into a search and storage problem. Also ask about multi-device support, because it multiplies every delivery by the number of linked devices.

02

Load estimates

03

Load estimates

Our assumptions: 900 million daily users, each sending about 75 messages a day. Thirty percent of messages go to groups, which average 12 members. Users have 1.3 active devices on average. At the evening peak, about a quarter of daily users hold an open connection at the same moment.

QuantityWorkingResult
Messages sent per day900M x 7567.5B
Send rate67.5B / 86,400 s, peak 2xabout 780,000 / s, peak 1.56M / s
Recipient copies per day70% x 1 + 30% x 11 other membersabout 270B
Device deliveries per day270B x 1.3 devicesabout 350B, 4M / s average
Receipts per dayone delivered plus one read per recipient copy, batchedroughly as many as deliveries
Open connections at peak900M x 25%about 225M
Connection hosts225M / 400,000 sockets per host, plus 50% headroomabout 850 hosts
Encrypted envelope sizetext plus headers and signaturesabout 350 B
Offline backlog at any moment8% of deliveries wait about 5 h: 350B x 0.08 x 5/24about 5.8B envelopes, 2 TB
Media uploaded per day5% of sends x 220 KB averageabout 740 TB, kept 30 days
Sizing the connection tier

A well-tuned host with an event loop or a lightweight-process runtime can hold several hundred thousand mostly idle TCP connections. The binding limits are memory per connection (TLS state and buffers, a few tens of kilobytes) and how fast the host can accept reconnections after a failure, not CPU. Plan capacity around the reconnect case.

Where the volume really comes from

Groups and devices multiply traffic. One message to a 12-person group where everyone has 1.3 devices produces about 14 deliveries and up to 28 receipts. Sending is under a million per second; delivering is four million. Design for deliveries.

03

Protocol and API

04

Protocol and API

Chat traffic does not use REST. Each device holds one long-lived, TLS-encrypted connection and exchanges small binary frames in both directions. The frames below are shown as JSON for readability. Only media upload and account management use ordinary HTTPS.

SENDframe: msgClient sends an encrypted message
{ "type": "msg", "client_msg_id": "3EB0A1F27C9D", // generated on the phone, used for dedup "chat": "g:120363044817", "payloads": [ { "device": "91987xxxx:0", "ciphertext": "base64..." }, { "device": "91987xxxx:3", "ciphertext": "base64..." } ], "sent_at": 1790845421 }
ACKframe: server_ackServer confirms durable storage (one tick)
{ "type": "server_ack", "client_msg_id": "3EB0A1F27C9D", "server_ts": 1790845421318 } // Sent only after every recipient inbox write is committed // If the client never sees this, it resends with the same client_msg_id
PUSHframe: deliverServer pushes queued messages to a device
{ "type": "deliver", "inbox_seq": 558201, // per-device, strictly increasing "from": "44770xxxx:0", "chat": "g:120363044817", "client_msg_id": "3EB0A1F27C9D", "ciphertext": "base64..." }
SENDframe: receiptDevice acknowledges delivery or read
{ "type": "receipt", "kind": "delivered", "up_to_seq": 558214 } // Cumulative: one frame acknowledges a whole batch, and the server can // delete those inbox entries. Read receipts travel to the sender the same way.
POST/v1/mediaUpload encrypted media
// Client encrypts the file with a random key first, uploads the ciphertext, // then sends the URL, key and hash inside an ordinary encrypted message // 201 { "media_url": "https://mm.example.net/v/t62/AbX...", "expires_in_days": 30 }

04

High-level design

05

High-level design

Message path from sender to every recipient deviceDiagram
flowchart LR
	A["Sender phone"] --> LB["TCP load balancer"]
	LB --> G1["Connection gateway A"]
	G1 --> RT["Message router"]
	RT --> GS[("Group membership")]
	RT --> IB[("Per-device inbox store")]
	RT --> REG[("Session registry")]
	RT -->|"recipient online"| G2["Connection gateway B"]
	G2 --> B["Recipient devices"]
	RT -. "recipient offline" .-> PN["Push notifier"]
	PN --> APNS["APNs or FCM"]
	A -->|HTTPS| MED["Media service"]
	MED --> BLOB[("Blob store")]

Clients connect through a layer-4 load balancer to a connection gateway, a host whose only job is to hold sockets, terminate TLS and move frames. When a frame arrives, the gateway hands it to a stateless message router. The router expands the chat into a list of recipient devices, appends a copy of the envelope to each device's inbox, then acknowledges the sender. Only after that does it try to deliver.

Delivery needs to know where each recipient device is connected right now. That lives in the session registry, a replicated in-memory map from device id to gateway id, updated whenever a device connects or disconnects. If the device is online, the router forwards the frame to that gateway, which writes it down the socket. If it is offline, the envelope simply waits in the inbox, and the router asks the push notifier to wake the phone. When the device reconnects, its gateway drains the inbox from the device's last acknowledged sequence number.

Connection gateway

Holds roughly 400,000 sockets per host. Keeps no message state that matters: if it dies, clients reconnect elsewhere and nothing is lost because the inbox holds the truth.

Session registry

About 290M device entries, each a few dozen bytes, so it fits in memory on a sharded cluster. Entries carry a connection epoch so a stale record from a dead gateway can be detected.

Message router

Stateless and horizontally scaled. Deduplicates on sender and client_msg_id, expands groups, writes inbox copies and forwards to gateways. Partitioned by chat id so one chat's messages are handled in order.

Inbox store

A wide-column store keyed by device id with the inbox sequence as the clustering key. Writes are appends, reads are short range scans, and acknowledged entries are deleted, so the table stays small.

Group membership

Group id to member list and each member's device list, cached heavily. Changes to membership are themselves messages in the group so every device sees them in order.

Key directory

Stores public identity keys and one-time prekeys for every device, so a sender can start an encrypted session with a device that is offline. It never sees private keys.

05

Deep dive: finding the recipient and not losing anything

06

Deep dive: finding the recipient and not losing anything

Two questions decide the design

First, when a message arrives on gateway 17, how do you know the recipient's phone is on gateway 412? Second, how do you guarantee it arrives exactly once on every device, given that phones drop off networks constantly? The registry answers the first, and the inbox plus acknowledgements answer the second.

The session registry

Gateways are stateful in one important way: a socket lives on exactly one host. So routing is a lookup, not a broadcast. When a device connects, its gateway writes device_id → (gateway_id, epoch) into the registry, and deletes it on clean disconnect. The router reads that entry for each recipient device and sends the frame to the right gateway over an internal RPC connection that is kept open between every router and every gateway.

The tricky part is stale entries. If a gateway crashes, its entries remain until something removes them, and messages forwarded to it are lost in transit. That is acceptable only because forwarding is an optimisation on top of the inbox, not the delivery mechanism. The message was already in the inbox before forwarding began, so whichever gateway the device reconnects to will drain it. The epoch number stops a late disconnect from an old connection overwriting a newer connect from the same device.

Delivery and acknowledgement for one deviceDiagram
flowchart TD
	S["Router receives msg"] --> D{"Seen this client_msg_id?"}
	D -->|yes| ACK["Re-send server_ack, stop"]
	D -->|no| W["Append to each device inbox"]
	W --> ACK2["server_ack to sender"]
	ACK2 --> L{"Registry: device online?"}
	L -->|yes| F["Forward to its gateway"]
	L -->|no| P["Send silent push"]
	F --> R{"Device sends delivered receipt?"}
	R -->|yes| DEL["Delete inbox entries up to seq"]
	R -->|"no, timeout"| KEEP["Keep in inbox for next connect"]
	P --> KEEP
	KEEP --> RC["Device reconnects and drains from last seq"]
	RC --> R

At-least-once delivery, exactly-once display

There is no way to deliver exactly once across an unreliable network. A phone can receive a frame and die before sending its receipt, and the server has no way to know which happened. So we deliver at least once and remove duplicates at both ends. The sender attaches a client message id and retries with the same id until it sees the server ack; the router keeps a short-lived dedup set of sender and id, so a retry produces no second copy. Each device inbox hands out a strictly increasing sequence number, and the device keeps the highest number it has stored. A redelivered frame with a lower or equal number is dropped on arrival.

Acknowledgements are cumulative. A device that drains 300 queued messages after a flight sends one receipt saying "delivered up to 558,514", and the server deletes that range in one operation. That keeps receipt traffic proportional to batches rather than messages, which matters when receipts are as numerous as deliveries.

Groups and end-to-end encryption

With end-to-end encryption, the server cannot encrypt anything on the sender's behalf. For one-to-one chats, the sender's phone encrypts the message separately for each of the recipient's devices and for the sender's own other devices. For groups that would mean dozens of encryptions per message, so groups use sender keys: each member shares a symmetric key with the group once, through pairwise encrypted messages, then encrypts each group message one time. The router fans out the same ciphertext to every member device. When someone leaves, every remaining member rotates their sender key so the departed member cannot read new messages.

Ordering without a global clock

Each chat is processed by one router partition at a time, so messages from the same sender enter the inbox in send order. Across senders, the order is the order the router saw them. Clients display by that server order, not by phone clocks, which can be minutes off. Everyone in a group sees the same order, which is what people actually notice.

06

Trade-offs and choices

08

Trade-offs and choices

Chosen
One persistent connection per device

The server can push instantly, and both directions share one socket with a compact binary framing. Keepalives every few minutes are enough to hold NAT mappings open. The cost is a stateful gateway tier and careful handling of reconnects.

Lowest latency and battery
Fallback
HTTP long polling

Works through any proxy, which helps on locked-down corporate networks. Each poll carries full HTTP headers and reconnects regularly, so it uses more data and adds a round trip to every server push.

Only when sockets are blocked
Chosen
Store and forward, delete after delivery

The server holds only the undelivered backlog, about 2 TB, instead of every message ever sent. It fits the privacy model, since the server never needs history. Backups and new-device history move between the user's own devices, encrypted.

Small, private, cheap
Rejected
Permanent server-side history

Gives instant history on any new device and server search, as cloud chat apps do. It also means storing tens of petabytes a year and holding data that the encryption model is meant to keep off the server.

Conflicts with E2EE goals
Chosen
At-least-once with dedup at both ends

Retries are safe because ids and sequence numbers make them harmless. No distributed transaction between server and phone is needed, which is good, because one is not possible.

Achievable and correct
Rejected
Exactly-once delivery protocol

Requires the device and server to agree atomically on whether a frame was received. With phones that vanish mid-handshake, any attempt reduces to at-least-once with extra round trips.

Not achievable over this network
Chosen
Wide-column store for inboxes

Partition by device id, cluster by sequence. Appends and range deletes are exactly its strengths, it spreads 4 million writes per second across many nodes, and replicas in several zones keep acked messages safe.

Matches the access pattern
Alternative
Sharded relational database

Works and gives transactions you mostly do not need. Heavy delete churn creates bloat and vacuum pressure, and resharding a queue table at this write rate is painful.

Viable at smaller scale
Chosen
Favour availability for presence and typing

"Last seen" and typing signals are best effort and can be stale or dropped. They go over a separate, lossy path, so an overloaded presence system never slows messages.

Messages first
Chosen with care
Strong durability for message writes

The server ack waits for a quorum write in the inbox store. That costs a few milliseconds, and it is the line between "tick shown" and "message possibly lost".

Never ack before durable

07

Failure scenarios

09

Failure scenarios

🌩️
A gateway host dies with 400,000 connections

Every phone on that host notices within a keepalive interval and tries to reconnect. If they all retry at once, the load balancer sends a burst of TLS handshakes to the remaining hosts, and the CPU cost of those handshakes can knock over a second host, then a third.

Clients reconnect after a random delay with exponential backoff, TLS session resumption makes handshakes cheap, and each gateway caps how many new connections it accepts per second. Messages are safe in inboxes throughout, so slow reconnection costs latency, never data.
🧭
The session registry loses a shard

Routers cannot tell where a slice of devices is connected. Live forwarding to those devices stops, though the devices still have healthy sockets.

Fall back to inbox plus a nudge: write the message to the inbox as always, then ask every gateway to check whether it holds that device, or simply wait for the device's periodic inbox poll, which runs every minute when the registry is degraded. Rebuild the shard by having gateways re-register their live connections.
👥
A huge group becomes very active

A 1,000-member community group gets a burst of 50 messages a minute during a match. Each message turns into about 1,300 device writes and as many receipts, all keyed to one chat partition.

Cap group size, batch inbox writes per destination node, and aggregate read receipts on the server for large groups so senders see counts, not per-member ticks. Very large broadcast use cases belong in a separate channels product with a pull model.
🔕
Push notifications are throttled

The mobile OS push provider starts rejecting or delaying notifications during a holiday spike. Offline users do not wake up and messages sit unseen for longer.

Collapse pushes per device so only one wake-up is pending at a time, prioritise one-to-one chats over groups, and retry with backoff. The messages are already stored, so a delayed push delays them without losing them.
🌐
A region becomes unreachable

A cross-ocean link fails. Users homed in the other region cannot receive messages routed through the broken path, and senders keep retrying.

Home each device's inbox in one region with an asynchronous replica in another. Messages to unreachable regions are queued at the sending side and replayed when the link returns; dedup ids make the replay safe.

08

Mistakes interviewers look for

⚠

Mistakes interviewers look for

🚫
Sending the ack before the message is stored

If the router acks first and crashes before writing the inbox, the sender sees a tick for a message that no longer exists anywhere.

Instead: ack only after a durable quorum write of every recipient copy.
🚫
Broadcasting each message to all gateways

With 850 gateway hosts, every message becomes 850 internal messages so that one of them can reach the right socket. Internal traffic would dwarf user traffic.

Instead: look the device up in a session registry and forward to exactly one gateway.
🚫
Ordering messages by the phone's timestamp

Phone clocks drift and can be changed by the user, so a reply can appear above the message it answers.

Instead: order by the server's per-chat sequence and keep the client time only for display.

09

Interview advice

10

Interview advice

1

Count deliveries, not sends. Show how groups and multiple devices turn under a million sends a second into several million deliveries. It justifies the inbox design and the receipt batching.

2

Draw the session registry. Many candidates draw gateways and never say how a message finds the recipient's host. Making the registry explicit, with an epoch for stale entries, is a clear signal.

3

Make the inbox the source of truth. Then gateway crashes, registry faults and dropped pushes become latency problems, not data loss. Say this sentence out loud.

4

Explain the three ticks precisely. One tick means durable on the server, two means a device stored it, blue means the user opened the chat. Map each to the exact event in your design.

5

Say "at least once, with dedup" without hedging. Then show the two ids that make it safe: the client message id and the per-device inbox sequence.

6

Know enough about end-to-end encryption. You do not need the full protocol, but explain per-device encryption, prekeys for offline recipients and sender keys for groups, and how that shapes server fan-out.

7

Plan for the reconnect storm. The worst moment for this system is not peak traffic but a host or region coming back. Backoff with jitter and admission limits per gateway are the expected answers.

8

Keep presence separate. Treat last seen and typing as lossy and rate-limited. If they share a path with messages, an interviewer will ask what happens when presence overloads it.

10

How the design grows

12

How the design grows

Stage 1: thousands of users
One server, sockets in memory, a messages table

A single process keeps a map of user to socket and writes each message to a database table before forwarding. It works because every connection is on the same machine, so no routing is needed.

Stage 2: a few million users
Several gateways and a registry

One machine can no longer hold every socket. Split connection handling from message logic, add the session registry, and switch to per-device inboxes with acknowledgements and deletion.

Stage 3: hundreds of millions
Partitioned routers and a wide-column inbox

The relational messages table cannot keep up with delete churn. Move inboxes to a wide-column store, partition routers by chat, batch receipts and add end-to-end encryption with a key directory.

Stage 4: global, multi-device
Regions, companion devices and edge presence

Users are homed in regions near them, companion devices get their own identities and inboxes, and points of presence close to users terminate connections to cut latency on mobile networks.

Watch and read

Try next

Free to read · better with landed

Whiteboard this with landed

landed runs it as a live system design round on the whiteboard and grades your trade-offs.