System Design Road

Hard Interview walkthrough

Chat app: the interview, played out

You probably sent a chat message in the last hour. It arrived in a fraction of a second, in the right order, with ticks telling you when it was delivered and read. This is how to design that, for 50 million people at once.

9 min read6 interactive diagrams12 sections

This gives away every stage. For a clean attempt, practise it first, then come back.

The short version

  • Keep a connection open to every phone so the server can push messages instantly. Polling is too slow and wasteful.
  • Order messages with a sequence number per conversation, never by the phone’s clock.
  • Save the message before showing “sent”, and let the phone create the message ID so retries can’t make duplicates.
  • With many connection servers, a registry says which one holds each user.
  • Offline phones catch up by asking for everything after the last sequence number they have.

The 45 minutes

Before we start

WhatsApp, Telegram, Slack: you type, you hit send, and the message is on your friend’s phone almost before you’ve put yours down. If they’re offline, it waits patiently and arrives the moment they’re back. You see one tick, then two, then blue.

Three things make this hard. Speed: messages must be pushed to phones instantly, which means keeping millions of connections open. Order: replies must never appear before the question they answer. Reliability: a message that shows “sent” must never be lost, and retries must never show it twice.

Same order as always: requirements, quick maths, data and API, a design that grows, then deep dives.

Minutes 0–5Requirements

Interviewer

Interviewer: Design a chat app like WhatsApp.

You

You: One-to-one chats only, or groups too? How big can groups get?

Interviewer

Interviewer: Both. Groups of up to 500 people.

You

You: How many users and messages?

Interviewer

Interviewer: 50 million people a day, about 40 messages each. Around 20 million are connected at the same time at peak.

You

You: What happens when the other person is offline?

Interviewer

Interviewer: The message waits and arrives when they’re back. They also get a push notification.

You

You: Do we need the sent, delivered and read ticks?

Interviewer

Interviewer: Yes, all three.

You

You: Do people use more than one device?

Interviewer

Interviewer: Yes: a phone, plus web or desktop. Each shows the full history.

You

You: Do we keep history forever, and images?

Interviewer

Interviewer: Text forever. Images yes. Voice and video calls are out of scope.

You

You: Online status and “last seen”?

Interviewer

Interviewer: Nice to have.

Functional requirements

  1. Send and receive one-to-one messages in real time.
  2. Group chats of up to 500 people.
  3. Messages to offline users wait, and arrive when they come back, with a push notification.
  4. Sent, delivered and read ticks.
  5. Full history on every device.

Nice to have: online status and last seen. Out of scope: voice and video calls.

Non-functional requirements

  1. Fast: when both people are online, a message arrives in under about 200 ms.
  2. Never lost: once the sender sees a tick, the message is safe.
  3. In order: messages in a conversation always appear in the same order for everyone.
  4. No duplicates, even when phones retry on a bad connection.
  5. Scale: about 20 million open connections and 70,000 messages a second at the busiest time.

Minutes 5–10Back-of-the-envelope estimates

You

You: 50 million people times 40 messages is 2 billion messages a day, about 23,000 a second, maybe 70,000 at the busiest time.

You: Storage: at about 200 bytes a message, that’s 400 GB a day, around 146 TB a year, and we keep it forever.

You: The unusual number is connections: 20 million phones each holding a connection open. If one server can hold around 500,000, we need about 40 connection servers, plus spares.

WhatWorkingResult
Messages50M users × 40 ÷ 86,400 s≈ 23,000 a second (busiest ~70,000)
Connections20M connected ÷ ~500,000 per server≈ 40 servers, plus spares
Storage per day2 billion messages × 200 bytes≈ 400 GB
Storage per year400 GB × 365≈ 146 TB, kept forever

500,000 per server is a safe plan. WhatsApp famously got about 2 million connections onto one carefully tuned server, which lesson 7.1 tells the story of. Leaving headroom means a server failing or being updated doesn’t overload the others.

Minutes 10–15Core entities and the API

Core entities

  • User and device: a person, and each phone or laptop they use.
  • Conversation: a one-to-one chat or a group, with its members and its latest sequence number.
  • Message: belongs to a conversation, has a sequence number within it, a sender, the text (or an image link), and an ID created by the sender’s phone.
  • Receipt: “delivered” or “read” up to a certain message, per member.

The API

Most chat traffic travels over one open WebSocket connection per device, not separate HTTP requests:

FUNCTIONws.send(message)

Sent over the open connection. The phone creates the message ID, so a retry can’t create a duplicate.

Query
{ "client_msg_id": "9f2c…", "conversation_id": "c_77", "text": "Running late!" }
Returns
{ "ack": "9f2c…", "seq": 1042, "status": "sent" }
FUNCTIONws.receive()

The server pushes new messages and ticks down the same connection.

Returns
{ "conversation_id": "c_77", "seq": 1043, "from": "meera", "text": "No problem" }
GET/v1/conversations/c_77/messages?after_seq=1030

Catch up after being offline: everything after the last message this device has.

Responses
  • 200{ "messages": [ { "seq": 1031, "text": "…" } ], "latest_seq": 1043 }

Minutes 15–25The design, built up

Stage 1Ask the server for new messages every few seconds

PhonesApp serverDatabasemessagesanything new? (3 s)Slow: up to 3 seconds late.Wasteful: most checks find nothing.
92%
You

You: The simplest version: messages go into a database, and every phone asks “anything new for me?” every three seconds.

It works for a small app. But messages can be up to three seconds late, and almost every one of those requests finds nothing new, so the server spends most of its effort saying “no”. Lesson 7.1 compares the options:

Meera sends four messages in 15 seconds
avg delay 2.1 s
Meera sendsApp’s requestsPriya receives0 s5 s10 s15 s+0.9 s+2.6 s+2.1 s+2.7 s

Messages wait up to 3 seconds, and the app makes 20 requests a minute whether or not anything arrives. Across 5M online phones, that’s over 1.6M requests a second.

Priya’s app uses

Stage 2Open connections: the server pushes messages

Phones and laptopsWebSocket gatewaysone WebSocket serverNEWMessage serviceorder, save, routeNEWMessage storeby conversationNEWnew messagesave with seq
58%
Green: new in this stage
You

You: Better: each phone opens one WebSocket connection and keeps it open. A WebSocket is a connection that stays open in both directions, so the server can send a message down it the moment it arrives.

You: When you send a message, a message service gives it the next sequence number for that conversation, saves it, tells your phone “sent”, and pushes it down the other person’s connection.

This is instant and efficient. But one server can only hold so many connections, and with 20 million phones we need about 40 servers. That creates a new problem.

Stage 3Many gateways: find the right one, route between them

Phones and laptopsLoad balancerNEWWebSocket gatewayshold open connectionsConnection registryuser → gatewayNEWMessage serviceorder, save, routeMessage storeby conversationRoutingpub/sub to gatewaysNEWWebSocketI hold user Xnew messagesave with seqdeliver
56%
Green: new in this stage
You

You: Now you’re connected to gateway 4, and Meera is connected to gateway 17. How does your message reach her?

You: When a phone connects, its gateway records “Meera is on gateway 17” in a fast shared registry, like Redis. To deliver, the message service looks Meera up and sends the message through a routing layer to gateway 17, which pushes it down her connection.

You: The registry entry expires unless the gateway keeps renewing it, so if a gateway crashes, its users simply disappear from the registry and are treated as offline until they reconnect elsewhere.

Lesson 7.1 shows a message finding its way across gateways:

Priya sends Meera a message
delivered · 2 devices
Priyagateway gw-4Chat serviceMessage storeConnection registrymeera: phone→gw-17, laptop→gw-9gw-17gw-9Meera’s phoneMeera’s laptopPush service

Delivered to both of Meera’s devices, through the gateways they’re connected to. Priya’s ticks turn to “delivered.”

Meera is

Stage 4Offline users, presence and images

Phones and laptopsLoad balancerWebSocket gatewayshold open connectionsConnection registryuser → gatewayMessage serviceorder, save, routeMessage storeby conversationRoutingpub/sub to gatewaysPush notificationsfor offline usersNEWPresenceheartbeatsNEWMedia storageimagesNEWWebSocketI hold user Xnew messagesave with seqdeliverif offlineheartbeatupload image
52%
Green: new in this stage
You

You: Three more things. If Meera isn’t in the registry, she’s offline: her message is already saved, so we send a push notification. When her phone reconnects, it asks for everything after the last sequence number it has, one quick read.

You: Presence, the “online” and “last seen” labels, comes from heartbeats the phones send. We only send someone’s status to people who currently have that chat open, otherwise every phone coming online would notify hundreds of contacts.

You: Images are uploaded straight to object storage, and the message just carries a link.

Here’s how the ticks work, online and offline:

Where the three ticks come from
read
Priya✓✓ readGateway 4Message serviceseq 1042, savedGateway 17Meeraopened the chat

Blue ticks: Meera opens the chat, and one more small message travels back saying 'read up to 1042'.

Meera is

Checking the design against the requirements

RequirementWhere the design handles it
One-to-one messages in real timeWebSocket gateways push messages instantly
Groups of up to 500Saved once, delivered to each online member, push to the rest
Offline delivery with a pushSaved first; push notification; catch-up by sequence number
Sent, delivered, read ticksSmall confirmation messages flowing back to the sender
History on every deviceEach device asks for everything after its last sequence number
FastOpen connections; no polling
Never lostSaved before the “sent” tick
In orderSequence number per conversation
No duplicatesMessage ID created by the phone; repeats are ignored
20M connections, 70K messages a second~40+ gateways, registry for routing, store split by conversation

Deep diveReal-time delivery

Interviewer

Interviewer: Why WebSockets? Talk me through the options.

Bad solutionPolling

The phone asks “anything new?” every few seconds. Messages arrive late, and most requests are wasted. Polling more often makes it faster but even more wasteful.

Good solutionLong polling

The phone asks, and the server holds the request open until there’s something to send, then the phone immediately asks again. Much faster. But connections are constantly torn down and rebuilt, and sending from the phone needs separate requests.

Great solutionWebSockets

One connection stays open both ways. The server pushes messages, ticks and typing indicators the moment they happen, and the phone sends over the same connection. The trade-off is that servers must hold millions of open connections, and we need the registry to know who’s where.

Deep diveKeeping messages in order

Interviewer

Interviewer: How do you make sure everyone sees messages in the same order?

Bad solutionOrder by the phone’s clock

Phone clocks can be wrong by minutes. Order by them, and a reply can appear above the question it answers.

Good solutionOrder by the server’s clock

Better, but different servers’ clocks also drift a little, and two messages can arrive in the same millisecond.

Great solutionA sequence number per conversation

The message service gives each message in a conversation the next number: 1041, 1042, 1043. Everyone sorts by it, so everyone sees the same order. Each conversation has its own counter, so busy conversations never slow each other down, and we never need a single global order across the whole app. Lesson 7.1 shows what goes wrong otherwise:

Priya sends two messages quickly, on a flaky connection
wrong order + duplicate
PriyaServer A · clock 8 ms slowServer B · clock correctConversationMeera seesTo the match? · .002Are you coming? · .006To the match? · .410

Out of order and duplicated. Server A’s slow clock stamped the second message earlier than the first, and the retry was stored as a new message.

Order by

Deep diveNever lost, never twice

Interviewer

Interviewer: A user on a train sends a message, the connection drops, and the phone retries. What happens?

You

You: Two rules handle this.

You: First, the message service saves the message before replying “sent”. If the server crashed before saving, the phone never got its tick, so retrying is safe and correct.

You: Second, the phone gives every message its own ID when it’s created. If a retry arrives with an ID we’ve already saved, we return the original result instead of saving it again. So retrying is always safe, and the message appears once.

You: The same idea protects the receiving side: a phone that’s sent a message with sequence number 1042 twice just ignores the second copy.

Deep diveGroup chats

Interviewer

Interviewer: How does a message reach a 500-person group?

You

You: Save it once, in the group’s conversation, with one sequence number. Then deliver a copy to each member who’s online through their gateway, and send push notifications to the rest. 500 deliveries is fine.

You: For huge groups or broadcast channels with tens of thousands of members, I wouldn’t push to everyone. Members fetch new messages when they open the channel, just like reading a feed.

You: And read receipts in big groups are usually summarised, like “read by 230”, because tracking 500 separate ticks for every message is a lot of traffic for little value.

Deep diveStoring billions of messages forever

Interviewer

Interviewer: Where do messages live?

You

You: In a database that spreads data across many machines, like Cassandra, with the conversation ID as the partition key. Then a whole conversation lives in one place, already sorted by sequence number, and “give me everything after 1030” is one quick read.

You: Very active conversations would grow huge partitions, so I’d add a time bucket to the key, like conversation plus month.

Discord stores its messages exactly this way, channel ID plus a time bucket. Lesson 3.1 tells that story:

Discord’s message table, 2017
read
APIload channel historyBusy channelone partition: 4 GBQuiet channelone partition: 2 MBQuerynewest first

Loading the latest 50 messages is still one partition read, but that partition is a giant.

Partition key

The complete architecture

Here’s everything together: the full design from the last stage, with every part we added along the way. If you only remember one picture from this question, make it this one.

Phones and laptopsLoad balancerWebSocket gatewayshold open connectionsConnection registryuser → gatewayMessage serviceorder, save, routeMessage storeby conversationRoutingpub/sub to gatewaysPush notificationsfor offline usersPresenceheartbeatsMedia storageimagesWebSocketI hold user Xnew messagesave with seqdeliverif offlineheartbeatupload image
52%

How one request travels through it:

  1. Each phone keeps one WebSocket connection open to a gateway server, and the gateway records ‘I hold this user’ in the connection registry.
  2. You send a message. Your phone gives it its own ID and sends it down the connection.
  3. The message service gives it the next sequence number for that conversation, saves it, and only then tells your phone it’s sent (one tick).
  4. It looks up which gateway holds the other person and sends the message there through the routing layer. That gateway pushes it to their phone.
  5. Their phone confirms it arrived (two ticks), and later that it was read (blue ticks). Those confirmations travel back the same way.
  6. If they’re offline, a push notification goes out instead. When they reconnect, their phone asks for everything after the last sequence number it has.

How this answer gets scored

Interviewers don’t tick boxes, but their notes on this question tend to land on the same few moments. Here’s what each one sounds like from the other side of the table.

MomentWhat earns creditWhat costs you
The first five minutesAsking about open connections, offline delivery and devicesAsking only about messages per second
Real timeWebSockets, and why polling falls shortPolling every few seconds
Many serversA registry of who’s connected whereAssuming everyone’s on one server
OrderA sequence number per conversationSorting by the phone’s clock
ReliabilitySave before “sent”, and a phone-made message IDAcknowledging before saving
StoragePartition by conversation plus time bucketOne big messages table

What the left column has in common: every choice points back to a requirement or a number from earlier. If you can say where each box came from, the interviewer will notice.