Design WhatsApp
Deliver 50 billion end-to-end encrypted messages a day to phones that are often offline, with sent, delivered and read ticks, and a server that cannot read any of it.
Last updated 2026-09-30. Difficulty: hard. Patterns: persistent-connections, store-and-forward, end-to-end-encryption, delivery-receipts, push. Reported at Meta, Amazon, Microsoft, Google, Uber.
The interviewer asks, the candidate answers and draws, and you press Next. Pause to answer yourself at the key decisions, and ask the AI Mentor anything along the way.
Functional requirements
- Send a one-to-one message. Text first; media handled as an encrypted attachment referenced by the message.
- Deliver to offline recipients. Messages wait on the server until every recipient device has received them, then are deleted. The server is a mailbox, not an archive.
- Sent, delivered and read receipts. One tick when the server has it, two when each recipient device has it, blue when read. Receipts are messages too.
- Group chats. Up to 1,024 members. Same ordering and receipt guarantees as one-to-one.
- End-to-end encryption. Only the participants’ devices can read content. The server routes ciphertext and cannot decrypt it.
- Multiple devices per account. A phone plus up to four linked devices, each with its own keys, all receiving every message.
- Out of scope. Voice and video calls, status/stories, payments, contact discovery and cloud backup (mention it is client-encrypted).
Non-functional requirements
- Scale (1 B DAU · 50 B messages/day). About 580 k messages a second on average, three times that at peak, plus two receipts per message.
- Delivery latency (p99 < 500 ms when both are online). Chat feels broken above a second. Online delivery must not touch slow storage on the hot path.
- Reliability (no message lost once the sender sees one tick). This is the requirement that drives the hardest trade-off: the server must persist before acknowledging, yet stay fast, and must deliver at least once to every device without showing duplicates.
- Ordering (per conversation). Messages in one chat appear in the order the sender sent them. No global order is needed.
- Privacy (server never holds plaintext). End-to-end encryption by default. The server still sees metadata (who talks to whom, when), which must be minimised and not logged longer than needed.
- Connections (~300 M concurrent). Phones keep a persistent connection while the app is active; the gateway tier is sized by connections, not requests.
- Battery and data. Mobile clients on poor networks: small frames, few round trips, and push notifications instead of polling when the app is in the background.
Back-of-envelope estimates
- Messages per second: ~580 k avg · ~1.7 M peak. 1 B DAU × 50 messages a day = 50 B/day ÷ 86 400 ≈ 580 k/s; × 3 at peak ≈ 1.7 M/s.
- Total frames per second incl. receipts: ~1.7 M avg. Each message produces a delivered and a read receipt, so 3× the message rate: 580 k × 3 ≈ 1.7 M frames/s on average. Receipts are smaller but count the same for routing.
- Concurrent connections: ~300 M. About 30 % of daily users are connected at any moment at peak: 300 M long-lived TCP connections.
- Gateway servers: ~600. A tuned server holds ~1 M mostly idle connections (WhatsApp reported over 2 M per box on Erlang). Plan for 500 k to leave headroom: 300 M ÷ 500 k = 600 servers. The tier is sized by memory per connection, not CPU.
- Mailbox size at any moment: ~1–2 TB. Say 10 % of messages wait for an offline device, for 6 hours on average. 50 B × 10 % × 1 KB (ciphertext + envelope) = 5 TB/day passing through; resident ≈ 5 TB × 0.25 day ≈ 1.25 TB. Small, because delivered messages are deleted.
- Media uploaded per day: ~1 PB. If 10 % of messages carry media at ~200 KB average: 5 B × 200 KB = 1 PB/day of encrypted blobs. Kept only until downloaded or 30 days, so storage stays bounded.
- Deliveries for one 1,000-member group message: ~2,000. Every member’s every device gets a copy: 1,000 members × ~2 devices = 2,000 deliveries and up to 4,000 receipts flowing back. Group traffic, not one-to-one, dominates delivery load.
Components
- Sender app: Encrypts each message on the device for every recipient device, assigns a client message id, keeps it in a local outbox until the server acknowledges, and stores the conversation history locally.
- Recipient devices: A phone and up to four linked devices, each with its own identity key. Each device receives, decrypts and acknowledges independently, and sends delivered and read receipts.
- Key directory (identity keys · prekeys): Stores each device’s public identity key and a pool of one-time prekeys uploaded in advance, so a sender can start an encrypted session with an offline device. Never sees private keys.
- Chat gateways (persistent TCP / TLS): Hold one long-lived connection per online device: authenticate, parse frames, acknowledge, and push incoming messages down. Sized by connections (about 500 k each), and stateless apart from the sockets.
- Session registry (device → gateway): Which gateway each online device is connected to, written on connect and removed on disconnect, with a heartbeat-driven TTL so a crashed gateway’s entries expire on their own.
- Message router: For each envelope: persist it to the recipient device’s mailbox, look up whether the device is online, forward it to that gateway, or hand it to the push service. Expands group messages into per-device deliveries.
- Device mailboxes (Cassandra · key = device_id): A queue of undelivered envelopes per recipient device, ordered by server sequence. An envelope is deleted when that device acknowledges it. Holds only ciphertext and minimal routing metadata.
- Push service: Wakes offline devices through the platform push services with a content-free notification ("you have messages"), coalescing many messages into one push and respecting platform rate limits.
- APNs / FCM: Apple’s and Google’s push services. Best effort, rate limited, and outside our control: a push that never arrives must not lose a message, which is why the mailbox, not the push, is the source of truth.
- Group service: Group membership and admin rules. Returns the member device list the router fans a group message out to, and emits membership changes so clients rotate group keys.
- Group store (group → members): Membership lists keyed by group id, with a version that bumps on every change. Small (1,024 members at most) and read on every group message, so heavily cached.
- Media service: Hands out upload and download URLs for encrypted attachments. The sender encrypts the file with a random key, uploads the ciphertext, and sends the key and hash inside the end-to-end encrypted message.
- Encrypted blobs (object storage + CDN): Ciphertext of attachments, unreadable without the key that only participants have. Deleted after every recipient has downloaded it or after 30 days.
User flows
- Send a message to someone online. The core path: encrypt on the device, persist before acknowledging, forward over the recipient’s open connection, and let receipts flow back the same way.
- The sender’s app encrypts the message for each of the recipient’s devices and sends it over its open connection. Each device has its own session, so a message to someone with a phone and a laptop is two ciphertexts in one frame. The client message id lets the server and the recipient drop duplicates if the sender retries.
- The gateway hands the envelope to the router, which appends one copy to each recipient device’s mailbox. Persist first: once this write succeeds the message survives any crash. The write is keyed by device and uses the client message id for idempotency, so a retried send does not create a second copy.
- The server acknowledges to the sender: one tick.
- The router looks up each recipient device’s gateway and forwards the envelope. The registry says which gateway holds the device’s socket. Forwarding is a direct RPC between servers; the gateway writes the frame to the socket. Total server time is a few milliseconds.
- Each recipient device decrypts, acknowledges, and sends a delivered receipt; the server deletes its mailbox copy. The ack deletes the server copy; the receipt is itself a small message routed back to the sender’s devices, which show two ticks once every recipient device has confirmed. Read receipts follow the same path when the chat is opened.
- The recipient is offline. Store and forward: the mailbox holds the message, a content-free push wakes the phone, and the device drains its mailbox on reconnect.
- The router finds no session for the device, so the message waits in the mailbox. Nothing else is needed for correctness: the mailbox already has the message from the persist-first step.
- The router asks the push service to wake the device. The push carries no message content, only "you have new messages" (or the encrypted payload when the platform allows decrypting in a background extension). Many messages in a short window collapse into one push to save battery and stay under platform rate limits.
- The device connects, registers its session, and asks for everything after its last acknowledged sequence.
- The device acknowledges up to a sequence number; the server deletes those envelopes. One ack covers a range, so draining a thousand messages costs a handful of deletes. Delivered receipts for all of them are sent back to their senders in a batch.
- Envelopes never collected expire after 30 days. A device that never comes back (a lost phone) should not hold messages forever. The sender sees one tick indefinitely, which is the honest signal that the message was never delivered.
- Send to a 1,000-member group. The scale case. Sender keys let the client encrypt once instead of 2,000 times, and the server does the fan-out to every member device.
- The sender encrypts the message once with its sender key for this group. With pairwise sessions a 1,000-member group would mean ~2,000 encryptions and uploads from a phone. With sender keys, each member has already received the sender’s group key (pairwise-encrypted, once), so one ciphertext serves everyone.
- The sender uploads one envelope addressed to the group.
- The router fetches the current member device list. Cached aggressively and versioned. If the sender’s member_version is stale (someone joined or left), the send is rejected so the client can redistribute keys first; otherwise a removed member could still decrypt.
- The router appends a copy to each member device’s mailbox and forwards to online devices. About 2,000 small writes and lookups, done in parallel batches per mailbox partition and per gateway. The sender gets one tick after the fan-out is durably queued, not after all 2,000 writes, by persisting the group envelope once and fanning out from it.
- Receipts flow back but are aggregated. The sender does not need 4,000 individual receipt frames. The server keeps per-message counters and sends the sender the state change only when it matters (two ticks when every member device has it), and per-member detail on request.
- A gateway dies mid-conversation. At-least-once delivery with idempotent ids: nothing is lost, nothing is shown twice, and order within a chat holds.
- A gateway holding 500 k connections crashes; every device on it reconnects to another gateway. Clients reconnect with jittered backoff so 500 k devices do not all arrive in the same second. The load balancer spreads them over the remaining gateways.
- Stale session entries for those devices expire; new connections re-register. Entries carry a TTL refreshed by heartbeats, so a dead gateway’s entries disappear within seconds without anyone cleaning up. Until then, forwards to the dead gateway fail and the message simply waits in the mailbox.
- A sender whose message was not acknowledged resends it with the same client message id. The mailbox insert is conditional on the message id, so if the first attempt had been persisted the retry is a no-op and the server just acks again. The sender cannot tell which happened, and does not need to.
- Recipients sync from their last acknowledged sequence and receive anything they missed, in order. A message forwarded just before the crash but not acked is delivered again. The client drops it because it already has that message id. Ordering comes from the per-device sequence, plus a per-chat sender counter inside the ciphertext that the client uses to order and detect gaps.
- Start an encrypted chat and send a photo. The privacy path: prekeys published in advance let a sender establish a session without the recipient being online, the server only ever relays ciphertext, and media travels separately from its key.
- When a device registers, it uploads its identity public key, a signed prekey and 100 one-time prekeys. Private keys never leave the device. The one-time prekeys are consumed one per new session, and the device tops them up when the server reports the pool is low.
- The sender fetches a prekey bundle for each of the recipient’s devices.
- The sender computes a shared secret (X3DH) and encrypts the first message with the Double Ratchet. The first message carries the key material the recipient needs to derive the same secret. Every later message advances the ratchet, so compromising one message key does not expose past or future messages.
- The ciphertext travels the normal store-and-forward path. To the server it is an opaque blob addressed to a device. It can see sender, recipient device, size and time: metadata, not content.
- To send a photo, the sender encrypts it with a fresh random key and uploads only the ciphertext. The media service hands out a short-lived upload URL; the blob store and its CDN only ever see ciphertext. The message that follows carries the blob URL, the key, a hash and a tiny encrypted thumbnail.
- The recipient downloads the blob from the CDN, checks the hash and decrypts it. Downloads are resumable and can wait for Wi-Fi. The blob is deleted once every recipient device has fetched it, or after 30 days.
- If the recipient’s identity key changes (new phone), the sender’s app shows a security notice. Users who verified each other’s safety numbers learn that something changed. Without this notice, the key directory could silently swap in a key and read messages; this is the main trust assumption in the design.
Deep dives
Holding 300 million connections
How do you push a message to a phone within milliseconds, and how do you know which server it is connected to?
Chat needs the server to push: polling every second from a billion phones would drain batteries and cost billions of empty requests. So every online device keeps a persistent connection, about 300 M at peak. Connections are mostly idle, so the gateway tier is bound by memory per connection and file descriptors, not CPU.
Once a message arrives at one gateway, it must reach whichever gateway the recipient is on. That needs a registry from device to gateway that is updated millions of times a minute as phones connect and drop, and that heals on its own when a gateway dies.
- Persistent TLS connections to stateless gateways + a TTL’d session registry + direct forwarding chosen
- Polling or long polling over HTTP rejected
- A pub/sub broker topic per user situational: at smaller scale, using a broker such as Redis pub/sub that handles many cheap channels
The answer: Clients keep a single TLS connection to a gateway behind an L4 load balancer, sending small binary frames with a heartbeat every few minutes (tuned to stay under mobile carriers’ NAT timeouts). On connect the gateway writes device → gateway into a session registry with a TTL refreshed by heartbeats. The router forwards an envelope by RPC to that gateway, which writes it to the socket. Gateways are sized by connections: about 500 k each, so ~600 servers, using an event-driven runtime (WhatsApp famously used Erlang and reported over 2 M connections per box). When a gateway dies, its entries expire and clients reconnect elsewhere with jittered backoff; anything in flight is safe in the mailbox.
A phone switches from Wi-Fi to 4G. What happens to its connection?
The old TCP connection silently dies, because the IP address changed. The client notices the network change and immediately opens a new connection, which re-registers and syncs from its last acked sequence. The old session entry expires or is overwritten, and messages forwarded to the old gateway in that window wait in the mailbox and arrive on the sync.
How do you deploy a new gateway version without dropping 300 M connections at once?
Drain gateways a few at a time: stop accepting new connections, then close existing ones gradually over minutes with a "reconnect" frame so clients move to other gateways at a controlled rate. Never restart more capacity than the rest of the fleet can absorb with headroom.
Why not keep the session registry inside the gateways themselves?
Then the router would have to ask every gateway where a device is, or use consistent hashing so a device always lands on a predictable gateway, which fights with load balancing and failover. A separate registry keeps placement free and lookup a single key read.
How do you monitor this tier?
Connections per gateway, connect rate and reconnect rate (a spike means a failure or a bad deploy), heartbeat misses, time from server receipt to socket write, and registry lookup latency. End to end, a canary pair of devices sending messages every few seconds measures real delivery latency per region.
Never lose, never duplicate
The sender sees one tick. What do you guarantee from there, and how?
Phones lose connectivity constantly, servers crash, and acknowledgements get lost in both directions. Exactly-once delivery across an unreliable network is impossible as a protocol property; what users need is that every message arrives and none appears twice.
The way to get that is at-least-once delivery plus deduplication at the edges: retry everything until acknowledged, and give every message an id that the receiver uses to drop repeats. The server must persist before it acknowledges, and must track delivery per device, because a person’s phone and laptop receive at different times.
- Persist-then-ack into per-device mailboxes, at-least-once forwarding, client-side dedupe by message id, delete on ack chosen
- Forward first, persist only if the recipient is offline rejected
- Keep a server-side history per conversation (like Slack) situational: workplace chat, where server-side history and search are requirements
The answer: The sender assigns a client message id and keeps the message in its outbox until the server acks. The router writes one envelope per recipient device into a per-device mailbox with a conditional insert on the message id, then acks the sender. It forwards to online devices; each device acks by sequence number and the server deletes up to that sequence. Clients drop any message id they already have. Order within a conversation is carried by a per-chat counter inside the encrypted payload, so clients can order and detect gaps even if two envelopes arrive out of order. Undelivered envelopes expire after 30 days.
The server forwards, the recipient displays the message, then crashes before acking. What happens?
The message stays in the mailbox and is delivered again on reconnect. The client already stored it before displaying, so it recognises the message id and drops the duplicate, but still sends the ack. The user sees the message once.
How do you order messages when two arrive out of order?
Per-device server sequences order delivery from one mailbox, but a sender’s two messages could be routed through different servers. The sender includes a per-conversation counter inside the ciphertext; the recipient sorts by it and, if it sees a gap, waits briefly for the missing one before showing later messages.
What is the mailbox store, and why?
A partitioned, write-heavy store keyed by device id and clustered by sequence, such as Cassandra or a custom store. The workload is append, read a range, delete a range, with almost nothing kept long, so it fits a log-structured design. Size is only a terabyte or two because messages are deleted on delivery.
At 10× the message rate, what breaks first?
Mailbox writes, because every message (and every group fan-out copy) is a durable write before the ack. Add partitions, batch writes per partition, and for online recipients consider writing the mailbox entry and forwarding in parallel, deleting on ack. Gateways scale with connections, which grow with users rather than messages.
End-to-end encryption at scale
If the server cannot read messages, what can it still do, and how does a new chat start when the other person is offline?
End-to-end encryption means keys exist only on devices. The server routes, stores and forwards ciphertext. That removes server-side search, spam filtering on content and history sync from the server, and it means every multi-device user needs their own keys per device.
The practical problem is starting a session: classic key agreement needs both parties online. The Signal protocol solves it with prekeys that each device publishes in advance, and then ratchets keys forward with every message so a stolen key exposes as little as possible.
- Signal protocol: X3DH with published prekeys + Double Ratchet, per device; sender keys for groups chosen
- Transport encryption only (TLS), server stores plaintext rejected
- One shared key per conversation rejected
The answer: Each device generates an identity key pair, a signed prekey and a batch of one-time prekeys, and publishes only the public halves to the key directory. A sender fetches a bundle per recipient device, runs X3DH to derive a shared secret, and uses the Double Ratchet for every message, so each message has its own key. For groups, each member distributes a sender key to the others over pairwise sessions; group messages are encrypted once with it and rotated when membership changes. Linked devices each have their own identity, and senders encrypt for every device of every recipient. Users can compare safety numbers to detect a swapped key. This is the protocol WhatsApp adopted from Signal.
A user links a new laptop. Can it read old messages?
Not from the server, which has none. The phone can transfer recent history to the new device over an end-to-end encrypted channel during linking; older history stays on the phone or in the user’s own encrypted backup. New messages are encrypted for the laptop from the moment it is registered.
How do you fight spam if you cannot read messages?
With metadata and behaviour: account age, send rate, how many recipients are strangers, how often people block or report the sender. Reports can include the reported messages, which the reporting user’s device decrypts and sends voluntarily. Rate limits on new accounts messaging many non-contacts catch most bulk spam.
What does the server still learn?
Who messages whom, when, how often, and roughly how large the messages are. Minimise it: keep routing metadata only as long as delivery needs, avoid logging sender-recipient pairs, and consider sealed-sender designs where even the sender identity is inside the encryption.
Someone is removed from a group. How do you stop them reading new messages?
Every remaining member rotates their sender key and distributes the new one pairwise to the remaining members only. The server enforces membership version on sends, so no message goes out encrypted with a key the removed person still holds.
Group messaging
A message to a 1,000-member group: who encrypts, who fans out, and what does that cost?
With pairwise encryption, the sender’s phone would encrypt and upload one copy per member device: around 2,000 copies for a 1,000-member group, over a mobile connection. That is too slow and too costly for the sender.
Fan-out on the server is cheap by comparison, but it is still ~2,000 durable writes per message and up to 4,000 receipts coming back. Groups, not one-to-one chats, dominate delivery load.
- Sender keys (encrypt once) + server-side fan-out to member devices + aggregated receipts chosen
- Client-side fan-out with pairwise encryption situational: small groups, and the initial distribution of sender keys
- Server-side group history decrypted for members rejected
The answer: Members exchange sender keys pairwise when they join or when someone leaves. A group message is encrypted once with the sender’s current sender key and uploaded once with the group id and the member list version the sender used. The server rejects stale versions, then persists the group envelope and fans it out into each member device’s mailbox in parallel batches, forwarding to online devices. The sender gets one tick once the fan-out is durably queued. Delivered and read receipts are counted per message on the server and surfaced to the sender as state changes, with per-member detail on demand. Group size is capped (1,024) to bound fan-out.
Two members are added while a message is in flight. Do they receive it?
Only if it was sent against a member list that includes them. The send carries a member version; the server fans out to exactly that version’s members, and the new members, who do not yet have the sender key from before they joined, start receiving from the next message. That matches the intent: new members do not see what was sent before they joined.
Why cap groups at 1,024?
Every message’s cost grows with members: writes, forwards, receipts, and key rotations on every membership change. A cap bounds the worst case. For audiences of tens of thousands, a different product (broadcast channels) with one-way fan-out and no per-member receipts fits better.
How would you build broadcast channels with 1 M followers?
Drop per-recipient mailboxes and receipts: store each post once and let followers pull it when they open the channel, with a push only as a hint. It becomes a feed problem rather than a messaging one.
Photos, videos and voice notes
How do you send a 20 MB video end to end encrypted without routing it through the chat servers?
Media is most of the bytes (around a petabyte a day here) but only a small fraction of messages. Pushing it through the chat gateways would tie up connections built for tiny frames, and storing it server-side seems to conflict with end-to-end encryption.
The insight is that the attachment and its key can travel separately: the encrypted blob goes to ordinary object storage, and only the decryption key and a hash travel inside the end-to-end encrypted message.
- Encrypt the file with a random key, upload ciphertext to blob storage, send key + hash + URL in the message chosen
- Send media inline over the chat connection rejected
- Server-side media store with server-held keys rejected
The answer: The sender’s app compresses the media, encrypts it with a fresh random key, and uploads the ciphertext to object storage through a short-lived upload URL from the media service. The message, end-to-end encrypted like any other, carries the blob URL, the key, a hash of the ciphertext and a small encrypted thumbnail. Recipients download from the CDN-fronted blob store, verify the hash and decrypt. Blobs are deleted once every recipient device has downloaded them or after 30 days. Forwarding a file reuses the same blob and key, which is the one form of deduplication available.
A recipient opens the chat after 45 days. What do they see?
The message with its thumbnail (which was inside the message), but the full media may be gone from the server. The app says it is no longer available and asks the sender to resend, unless the sender’s device still has it. That is the cost of bounded server storage.
How do you show a preview before downloading?
A tiny thumbnail (a few KB) travels inside the encrypted message itself, along with dimensions and duration. The full download starts on demand or automatically on Wi-Fi, depending on the user’s settings.
The theory behind it
- Real-time systems: WebSockets, SSE and push: Long polling vs Server-Sent Events vs WebSockets, connection servers and registries, pub/sub fan-out, presence, reconnection and resync, and scaling to millions of connections.
- Distributed transactions and idempotency: Why cross-service transactions are hard, two-phase commit vs sagas, the outbox pattern, idempotency keys, exactly-once semantics, and how to keep money and inventory correct.
- Authentication, authorisation and data protection: Sessions versus tokens, OAuth and OIDC in the depth interviews ask for, service-to-service auth, authorisation models, secrets, encryption and the privacy requirements that change a design.