System design sample solution

System design: chat app

A step-by-step system design answer you could actually give in 60 minutes.

System design interview · Sample 60-minute answer · Not Uber's actual design · Independent, not affiliated with Uber

The system design question

Design a chat app like Messenger. Support one-to-one and group chats, people who are offline, delivered and read receipts, and message history. Then tell me how it scales.

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Sending messages in one-to-one and group chatsVoice and video calls
Delivering to people who are offlinePhotos and files, beyond a mention
Delivered and read receiptsEnd-to-end encryption
Message historySearch across all messages

Then agree on what "good" means:

  • Never lose a message: once the sender sees it went through, it's saved.
  • In order: everyone in a chat sees its messages in the same order.
  • Quick: if the other person's online, it shows up in well under a second.
  • Scales: hundreds of millions of people, most of them connected at once.

Rough numbers (5 minutes)

These are assumptions: 500 million daily users, 40 messages each a day, about 100 bytes of text per message, and groups capped at 500 people.

~230K
messages per second on average
~2 TB
of new messages a day
~100M
open connections at a peak
~1M
receipts per second
QuestionRough mathWhat it means
How many messages?500 million × 40 ÷ 86,400About 230K a second, and maybe double at a peak
How much storage?20 billion a day × about 100 bytesAbout 2 TB a day, so 700 TB a year. History has to be split across lots of machines
How many connections?Say 100 million online at once ÷ 100K per serverAbout 1,000 servers just to hold connections
How many receipts?Each message gets a delivered and a read, more in groupsA few times the message rate. They need to be cheap

The overall design (10 minutes)

Phoneskeep a connection openChat servershold connectionsMessage storesplit by chatDelivery queueper user inboxPush servicefor offline phones
Messages are saved first, then delivered live if the person's connected, or by push and catch-up if not.
  1. Each phone keeps a WebSocket open to a chat server while the app's open. A small directory says which server each online user is on.
  2. When someone sends a message, their chat server saves it to the message store under that chat, with the next sequence number, and only then tells the sender it went through.
  3. For each other member, it looks them up in the directory. If they're online, it forwards the message to their chat server, which pushes it down their connection.
  4. If they're offline, the message waits in the store and we send a phone push. When they come back, the app asks for everything after the last sequence number it has, per chat.
  5. Delivered and read receipts go the other way as small events, and we save the latest one per person per chat.

What the apps need to do

ActionWho does itWhat's sent
Send a messageApp, over the connectionChat ID, a client message ID, and the text
Get new messagesApp, when it reconnectsFor each chat, the last sequence number it has
Load older historyApp, scrolling upChat ID and a sequence number to read backwards from
Mark delivered or readAppChat ID and the sequence number it's reached
Create or change a groupAppMembers to add or remove

What we store and where

DataWhere it goesWhy
MessagesA wide-column store, keyed by chat then sequence numberHuge, write-heavy, read by chat in order
Chats and membersA relational databaseSmall, has relations, changes rarely
Each user's chat list, newest firstA key-value store keyed by userThe app's home screen
Read and delivered marksKey-value, one row per person per chatOverwritten in place, so it doesn't grow with messages
Who's connected whereAn in-memory store with expiryRouting live messages to the right server

Step 1: Sending, ordering and offline delivery (12 minutes)

Save first, then deliver. The sender's server writes the message to the store before it tells the sender it worked. Once that's done, a crash can't lose it, because anyone who missed the live push gets it when they catch up.

Order comes from a sequence number per chat. The chat's partition hands out the next number when it saves the message, so every member sees the same order. Don't use phone clocks, since they disagree.

Phones retry when they don't hear back, so a message can arrive twice. The app makes up a message ID before sending, and the server ignores one it's already saved. That's idempotency, and it means retries are always safe.

Coming back online

The app remembers the last sequence number it has for each chat. When it reconnects, it sends those, and the server returns anything newer. It's simple, and it works the same after five seconds offline or five days. While they're away, a phone push tells them there's something new.

Step 2: Groups and receipts (8 minutes)

A group message is saved once, under the group's chat ID. Only the delivery fans out: one forward per online member, one push per offline one. With a 500-person cap, that's fine to do right away.

Receipts are where the tiny writes pile up. Don't store a receipt per message per person. Store one number per person per chat: the highest sequence they've got and the highest they've read. Reading means everything up to there. That's one overwrite, not hundreds of rows.

Send receipts back through a queue and batch them. If someone scrolls through 50 messages, the app only needs to send the last one it read. In big groups, show 'read by 12' instead of pushing every single read to every member.

Step 3: Scaling storage and connections (10 minutes)

Messages are split across lots of machines by chat ID. That's sharding, and it puts a whole chat on one partition, so reading history is one ordered range read. A wide-column store fits this well: it's fast to write and reads rows for one key in order. That's why NoSQL wins for messages, while groups and members stay in a normal database.

Chat servers just hold connections and pass messages along. They don't keep anything that matters, so we can add more. When a phone connects, the server writes 'this user is on me' to the directory with a short expiry, and refreshes it while the connection's alive.

Old history gets read far less. Keep recent months on fast disks and move older chats to cheaper storage. Phones keep a local copy, so scrolling back usually doesn't hit the server at all.

  • A really busy chat: one partition takes all its writes. With a 500-member cap, that's still manageable.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
Save before deliveringDeliver first and save in the backgroundA message the sender saw go through is never lostWe wanted the lowest possible delay and could live with rare losses
A sequence number per chatTimestamps from phonesEveryone sees the same orderChats got so busy one partition couldn't hand out numbers fast enough
One read mark per person per chatA receipt row per message per personWay fewer writes and much less storageProduct wanted to show exactly when each message was read
Split messages by chatSplit by userA chat's history is in one place and stays in orderHuge broadcast channels, where one chat gets too hot

What could go wrong (5 minutes)

What happensWhat we do about it
A chat server diesIts phones reconnect to another server and catch up from their last sequence numbers
A message store partition is slowSends to those chats slow down or fail, and the app retries with the same message ID
The directory is out of dateA forward goes to the wrong server and fails, so we fall back to push and catch-up
Push notifications don't arriveNo message is lost. The user sees it next time they open the app
The same message is sent twiceThe server sees the message ID again and doesn't save a second copy
A big group posts a lot at onceDelivery fans out through the queue, and receipts get batched

Follow-up questions (5 minutes)

How would you add photos?

Upload the photo to file storage first, then send a normal message that holds a link to it. The chat path stays small.

How do you show who's online?

Use the same connection directory. Only send online and offline changes to people who have that chat open, not to every contact.

What about a user with several devices?

Each device has its own connection and its own last sequence numbers. Deliver to all of them, and sync read marks so reading on one clears the others.

How the 60 minutes add up

MinutesWhat you're doing
0–5Agree on what you're building
5–10Rough numbers
10–20Draw the overall design
20–32Sending, ordering and offline delivery
32–40Groups and receipts
40–50Scaling storage and connections
50–55What could go wrong
55–60Follow-up questions

Key concepts used here

Further reading

We wrote this sample answer ourselves.

Practice Loop is independent and isn't affiliated with or endorsed by Uber.