How I Designed WhatsApp in a 45-Minute Interview
Day two of designing a system a day and writing it up the way I'd actually talk through it in an interview. Today is WhatsApp, which is almost the opposite problem. Nothing here is big in bytes. A text message is tiny. The hard part is that there are billions of them, they have to arrive in order, they have to arrive fast, and they have to arrive even when the person you're messaging had their phone off for two days.
Series: System Design Walkthroughs (2 articles)
- 2How I Designed WhatsApp in a 45-Minute Interview This article
Why chat is deceptively hard
When the interviewer said design WhatsApp, the easy mistake is to think it's a simple CRUD app. You send a message, it goes in a database, the other person reads it. If that were the whole story nobody would ask it in interviews.
The hard parts show up once you look closely. Both people are usually not online at the same moment, so you need to hold messages and deliver them later. Phones drop off networks constantly, so you can never assume a connection stays up. People expect those little ticks that tell them a message was sent, delivered, and read. Groups mean one message has to turn into many. And the whole thing has to feel instant, which rules out everyone just polling a server every few seconds.
So the real question is how you move a small piece of data from one phone to another reliably and quickly when neither phone is dependable. That framing is what I led with, and it set up everything after.
What we're building
I spent the first few minutes agreeing on scope with the interviewer. We settled on:
- One to one messaging between two users
- Group messaging
- Delivery and read receipts, the single, double, and blue ticks
- Online and last seen presence
- Messages that get held and delivered when someone comes back online
- Media like photos and voice notes
And what I deliberately left out so I wouldn't drown: voice and video calls, the encryption protocol internals, and multi device sync. I mentioned end to end encryption exists and said I'd treat it as a layer rather than redesign the Signal protocol on a whiteboard. The interviewer was fine with that.
The numbers
A bit of rough math up front, because it tells you how serious the delivery path has to be.
Say there are two billion users and maybe half of them are active on a given day. If each active user sends around forty messages a day, that's on the order of tens of billions of messages a day. Spread across a day that averages out to roughly a million messages a second, and at peak you'd plan for several times that.
The messages themselves are tiny, maybe a hundred bytes of text. So the storage pressure isn't the text, it's the sheer count and the metadata, plus media, which is far larger and gets handled separately. The thing that stood out when I said these numbers is that the system is enormously write heavy and connection heavy. You're holding hundreds of millions of live connections open at once, which is its own engineering problem before you even deliver anything.
The shape of the system
Here's what I drew.
Every phone keeps a persistent connection open to the backend. Not a fresh request per message, an actual long lived connection, usually a websocket, that stays up so the server can push a message down the instant it arrives. That one decision drives a lot of the design.
Those connections land on a tier of servers whose whole job is to hold connections. Call them chat servers or gateway servers. Each one keeps a big pile of open sockets. Because any user could be connected to any of these servers, you need a way to know, for a given user, which server they're currently sitting on. That's a session registry, basically a fast lookup from user to the server holding their connection.
When Alice sends a message to Bob, it travels up Alice's connection to whatever chat server she's on. That server needs to get it to Bob. It looks up where Bob is connected, finds the chat server holding Bob's socket, hands the message over, and that server pushes it straight down to Bob's phone. If Bob isn't connected at all, the message goes into storage to wait for him.
So the one line version: every phone holds an open connection to a chat server, a registry tracks who is on which server, and delivering a message is really just finding the recipient's server and pushing the message down their socket, or parking it if they're offline.
The connection tier
I spent real time here because it's the heart of it.
Each chat server holds a huge number of persistent connections. When a phone connects, the server authenticates it and records in the session registry that this user is now reachable through this server. When the phone drops, that entry goes away or gets marked offline, which also flips the user's presence to offline.
The registry has to be fast and it changes constantly, every connect and disconnect is a write, so it lives in memory in something like Redis rather than a normal database. It maps a user to their current server and connection.
A nice property of pushing over an open socket is that you stop polling entirely. The server doesn't answer "any new messages for me" a hundred times. It just pushes the instant something arrives, which is both faster for the user and far cheaper for the fleet.
Actually delivering a message
Let me trace a one to one message, because the whole design lives in this path.
Alice sends to Bob. The message goes up Alice's socket to her chat server. First thing that server does is write it down, because you must not lose a message if something crashes a millisecond later. Then it looks up Bob in the registry.
If Bob is online, the server finds the chat server holding his socket, forwards the message there, and that server pushes it to Bob's phone. Bob's phone sends back an acknowledgment, which flows back toward Alice and turns her single tick into a double tick. When Bob actually opens the chat, his phone sends a read receipt, and that's the blue ticks.
If Bob is offline, there's nobody to push to, so the message sits in storage, in Bob's inbox or mailbox. The moment Bob reconnects, his chat server pulls everything waiting for him, pushes it down in order, and clears it from the pending store once he acknowledges it. This is the part that makes chat feel reliable. The sender doesn't care whether the recipient is online. They fire and forget, and the system guarantees it lands eventually.
One subtlety I called out is ordering. Within a single conversation messages should arrive in the order they were sent, so each message carries a sequence number or timestamp per conversation, and the client uses that to order them rather than trusting the raw arrival order over the network.
Storage
I didn't reach for one database for everything.
The pending messages, the ones waiting for an offline user, want very fast writes and reads keyed by user, and they get deleted as soon as they're delivered. That's a good fit for a wide column store like Cassandra, or even a queue per user. The access pattern is simple, give me everything waiting for this user, in order.
Here's a design choice worth saying out loud. WhatsApp historically did not keep your message history on the server once it was delivered. Once the message reached your phone, the server could drop it. Your history lives on your device, not in the cloud. That's a very different stance from something like Facebook Messenger, which keeps everything server side. The upside is less storage and better privacy. The downside is that if you lose your phone without a backup, the history is gone. I mentioned both so the interviewer knew it was a deliberate tradeoff and not an oversight.
Media is handled separately. You don't push a photo through the message pipe. The sender uploads the file to a blob store, gets back a URL or an id, and only that reference travels as the message. The recipient downloads the actual bytes from the blob store, usually fronted by a CDN. Keeping the big bytes out of the message path keeps delivery fast.
Groups
Groups are where a lot of candidates trip, so I was careful.
A group message is really fan out. Alice posts one message to a group of a hundred people, and the system has to deliver it to all hundred. The clean way is that the message goes up to a chat server, the server expands the group membership into the list of recipients, and then it runs the exact same delivery logic for each one. Online members get a push, offline members get the message parked in their inbox.
The thing to flag is that fan out gets expensive as groups get large. One message to a thousand person group is a thousand deliveries. WhatsApp caps group sizes partly for this reason. For very large fan out you'd lean on the encryption design too, where the sender encrypts once with a shared group key rather than separately for every member, which keeps the sender side cheap.
Presence and receipts
Presence, the online and last seen, sounds trivial and is actually a small firehose. If you tried to broadcast every status change to everyone who might care, you'd melt. So presence is lightweight and best effort. When you connect you're marked online in the registry, when you disconnect you're marked offline with a timestamp. Your contacts only find out your status when they look, or you push updates only to the small set of people currently in a chat with you rather than your whole contact list. It's fine for presence to be a little stale. Nobody cancels anything because last seen was thirty seconds off.
Receipts ride the same delivery rails as messages. A delivered receipt is just a tiny message flowing back from the recipient's phone, and a read receipt is another one sent when they open the chat. Treating them as ordinary small messages means I didn't need a separate system for them.
Scaling it
The chat servers scale horizontally. You add more boxes and spread connections across them, and because they mostly just hold sockets and push, they're fairly uniform. The thing you cannot do is pin everything to one place, which is why the session registry exists, so any server can find any user regardless of which box they're on.
The registry itself scales by sharding on user id, and it's in memory because it's hammered on every connect, disconnect, and lookup. The pending message store shards on user id too, so one user's mailbox lives on a predictable set of nodes and a hot user doesn't drag everyone down.
Across regions you route users to a nearby data center to keep the connection latency low, and messages between regions hop across the backbone. You keep a user's data anchored to a home region so lookups are consistent rather than racing across the planet.
The honest hardest scaling problem here isn't throughput of messages, it's the number of simultaneous open connections. Holding hundreds of millions of live sockets is why WhatsApp famously ran on a small number of very carefully tuned Erlang servers, since that platform is built for exactly this kind of massive concurrency. I didn't need to design that part in detail, but naming it showed I knew where the real pressure was.
Keeping it from falling over
The interviewer pushed on failure, so I walked through a few things.
Messages are written down before they're acknowledged, so a server crashing mid delivery doesn't vaporize a message. The sender keeps retrying until it gets an acknowledgment, so a dropped connection just means a resend, and because each message has an id the receiver can throw away duplicates rather than show the same message twice. That combination, retry plus dedupe by id, is what gives you exactly once feel on top of an unreliable network.
For load, the connection tier can shed and rebalance. If a chat server dies, the phones it was holding simply reconnect and land on another server, which updates the registry. Rate limiting stops a single account from blasting the system, which also happens to be your first line of defense against spam. And presence and receipts, being best effort, are the first things you let lag when the system is under stress, because protecting actual message delivery matters more than a perfectly live last seen.
The rule, same as yesterday, is that a slightly degraded experience beats dropping someone's message. You let presence go stale long before you ever lose a text.
Walking through the whole thing once
To tie it together. Alice opens the app and her phone holds a connection to a chat server, which marks her online in the registry. She types a message to Bob and it goes up that connection. Her server writes it down, then looks Bob up. Bob is offline, so the message waits in his mailbox and Alice sees a single tick. Bob's phone comes back online an hour later, connects to some chat server, and that server pulls his waiting messages and pushes them down in order. Bob's phone acknowledges, Alice's tick turns double, and when Bob opens the chat a read receipt flows back and the ticks turn blue. If Bob had been online the whole time, the only difference is the message would have skipped the mailbox and gone straight to his phone in a fraction of a second.
What I'd tell someone walking into this one
Lead with the real problem, which is reliable fast delivery between two unreliable phones, not a database schema. Make the persistent connection and the session registry the center of your design. Have a clear story for the offline case, because that's what separates chat from a basic CRUD app. Treat receipts and presence as cheap messages on the same rails rather than new systems. Remember that groups are just fan out and that fan out gets expensive. And know that the hidden monster is the sheer number of open connections, not the size of the data.
That's day two. Tomorrow I'll pull apart another one. If you'd have designed the delivery path or the group fan out differently, I'd like to hear it, because these questions never have just one right answer.
If you want to go deeper, the system design primer repo by donnemartin on GitHub and the system design material on Hello Interview both cover chat systems well, and the old WhatsApp engineering talks about running millions of connections per server are a fun read.
Series: System Design Walkthroughs (2 articles)
- 2How I Designed WhatsApp in a 45-Minute Interview This article
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article