InterviewKata
System design · Worked design D07 of 46 · Social and content · Long form

Design a chat app

Send a message and it appears on your friend's phone a moment later, in order, exactly once, with a tick when it's delivered and another when it's read, even if their phone was off for a week. Chat looks simple and combines nearly every Level 4 idea: persistent connections, routing between servers, ordering, at-least-once delivery with deduplication, and offline sync. It follows the interview framework phase by phase.

1. The prompt

"Design WhatsApp" or "design Messenger": one-to-one and group chat on phones, with delivery and read receipts.

Most of the difficulty is invisible to users: routing a message between two of hundreds of connection servers, guaranteeing order and no duplicates over flaky mobile networks, and syncing a phone that was off for a week, all while the servers can't read what's being sent.

2. Frame the problem

One-to-one only, or groups too? How big?

Both; groups up to about 1,000 members.

Group delivery is fan-out; the size cap keeps it bounded (and matters for encryption).

Must messages reach people who are offline?

Yes, whenever they come back, plus a push notification.

Messages are stored before delivery, and devices sync by position.

Multiple devices per user?

Yes: a phone plus laptops and tablets.

Each device tracks its own position and has its own encryption keys.

Is the history kept on the servers forever?

Kept, so new devices can load it.

It's the biggest storage decision: petabytes a year versus a small buffer. Worth asking explicitly.

End-to-end encryption?

Yes. Servers must not be able to read message content.

Servers store and forward ciphertext; no server-side search of content.

Receipts, presence and typing?

Delivered and read receipts, online/last-seen and typing indicators.

These are high-volume and soft: they need cheap, lossy handling.

How many users?

500M daily users, 50M connected at peak, 20 billion messages a day.

Hundreds of connection servers and a write-optimised store.

3. What it must do

  1. A user can send a message to a person or a group, and it's delivered in order, once.
  2. Offline users get their messages when they return, on every device.
  3. Users see receipts and presence: delivered, read, online, typing.

Not now:

Photos and videos go to blob storage, and the message carries a reference (Module 10).

4. How well it must do it

TargetWhy this number
Fast: under ~200 ms to deliver when both users are online in one region speedChat feels like conversation only if it's near-instant.
Never lost, never duplicated, in order within a conversation data safety · consistencyA missing or reordered message changes what was said.
Scale: 50M concurrent connections, 600K messages a second at peak load shapeNew Year's Eve brings huge spikes.
Private: end-to-end encrypted securityUsers trust the app with their private lives.
Works on bad networks: reconnects and catch-up are routine, cheap on battery where it runsPhones switch networks, sleep and lose signal all the time.

5. Numbers that change the design

QuantityWorking→ so we…
Messages2 × 10¹⁰ a day ÷ 10⁵ s~200,000/s, ~600,000/s at peak → so a write-optimised, partitioned store
Connections50M concurrent; ~100–500K per gateway server→ so ~200–500 gateway servers holding WebSockets
Storage2 × 10¹⁰ × ~200 bytes (encrypted text and metadata)~4 TB a day, petabytes a year if kept → so time-bucketed partitions and tiered storage
Presence50M users, each with ~100 contacts, changing state several times a dayBillions of updates if broadcast → so only to people looking at the conversation

6. The nouns

messages       partition (conversation_id, time_bucket) · clustering seq desc
               client_msg_id, sender_id, encrypted_body, media_ref, sent_at
conversations  conversation_id, type, members[], last_seq
devices        (user_id, device_id) → push_token, last_acked_seq per conversation
presence       online:{user} with a 30 s TTL (soft state, in memory)

7. The contract

A WebSocket while the app is open (both directions), plus HTTPS for sync and media uploads. The user and device come from the authenticated session.

// over the WebSocket, both directions
→ send     {client_msg_id: "c9f1…", conversation_id, body}
← ack      {client_msg_id: "c9f1…", seq: 1043}            (stored: one tick)
← message  {conversation_id, seq: 1043, sender, body}
→ receipt  {conversation_id, up_to_seq: 1043, type: "delivered" | "read"}
// over HTTPS
GET /v1/conversations/{id}/messages?after_seq=1040&limit=100   (sync after reconnect)

8. First working version

Gateway servers holding WebSockets, a chat service, a message store and a push service.

8.1 Sending to someone online

The chat service stores the message, assigns its sequence number, acknowledges to the sender, then delivers it to the recipient's connection. Version one keeps a table of "which gateway holds which user".

8.2 Someone offline

The message is already stored; the chat service sends a push notification. When the app opens, it fetches messages after its last sequence number.

8.3 Receipts

When Ravi's phone shows the message, it sends "delivered up to 1043"; when he opens the chat, "read up to 1043". The chat service forwards them to Asha like any other message.

Parked for later (section 9 fixes each)

  1. What decides order when phone clocks disagree? What about resends after a lost ack?
  2. Keeping "user → gateway" accurate as phones reconnect to different servers.
  3. Phones with flaky networks miss messages in flight.
  4. Storage for 20 billion messages a day.
  5. A message to a 500-person group.
  6. Presence and typing updates would outnumber real messages.
  7. Encryption: what can the server see?

9. Make it hold up

9.1 Ordering and duplicates in order · never duplicated

ApproachWhy
✗ BadOrder by the sender's phone clockPhone clocks are wrong and differ (Module 20): replies appear before the messages they answer.
~ GoodOrder by server receive timeBetter, but several servers' clocks disagree too, and ties break unpredictably.
✓ GreatA per-conversation sequence number assigned when the message is stored (one partition per conversation orders its writes), plus the client's own client_msg_id so a resend after a lost ack is recognised and not stored twiceTotal order within each conversation, exactly what users perceive; retries are idempotent. No global order is needed.

9.2 Getting the message to the right server fast · scale

Asha's phone is connected to gateway 12, Ravi's to gateway 87. Gateways are thin: they hold connections and nothing else, so deploying chat logic doesn't drop 50M connections. When Ravi connects, gateway 87 subscribes to his channel in a sharded pub/sub layer (or registers "Ravi → gateway 87" in a session store with a TTL). The chat service stores the message, then publishes it to Ravi's channel; whichever gateway holds Ravi's connection pushes it down (Module 19).

9.3 Offline users, receipts and sync never lost · bad networks

9.4 Storing billions of messages scale · never lost

Writes dominate, and the read pattern is always "the latest messages in this conversation, then older ones": a perfect fit for a wide-column store such as Cassandra or ScyllaDB (Discord has written about its message storage), partitioned by (conversation_id, time bucket) and clustered by sequence, newest first (Module 13). Buckets keep huge, years-old groups from becoming giant partitions. Some apps keep messages on servers only until all devices have them; others keep full history. That choice changes storage by orders of magnitude, so ask.

9.5 Group chats scale

A message to a 500-member group is stored once in the group's conversation; delivery fans out to the members' channels (and push notifications for offline members). For very large groups or channels, members fetch on demand rather than receiving every message pushed, much like the celebrity case in Design D06. With end-to-end encryption, the sender's device encrypts for each member device (or uses a shared group-key scheme), one reason group sizes are capped.

9.6 Presence and typing scale · bad networks

Presence is soft state: each connection's heartbeat refreshes online:ravi with a 30-second TTL, and expiry means offline. Only send presence changes to people who are looking at the conversation, and throttle typing indicators. At 50M users, unfiltered presence updates would outnumber real messages. Losing a presence update is harmless; the next heartbeat fixes it.

9.7 End-to-end encryption private

With the Signal protocol (used by WhatsApp and Signal), keys live on devices; servers store and forward ciphertext they can't read. The server still knows metadata (who messaged whom, and when) and must deliver to every device of each recipient, since each device has its own keys. This rules out server-side search of message content and shapes how history reaches a new device.

9.8 The whole design after the deep dives

"See you at 7" from Asha to Ravi, then Ravi's laptop catching up later.

10. Interviewer follow-ups

New Year's Eve: 5× the normal messages in one minute.

Gateways are already sized for peak connections; the message path needs headroom in the store and pub/sub. Queue the non-urgent work (push notifications for offline users, receipts) behind real messages, and throttle presence and typing harder during the spike.

A gateway server dies with 200,000 connections.

Phones reconnect (with jittered backoff) to other gateways and resubscribe; then each syncs by sequence, so nothing is lost. Spread reconnects to avoid a stampede, and keep spare gateway capacity.

Broadcast channels with a million followers.

Treat them like a feed, not a chat: store once, don't push every message to every member; members pull when they open the channel, with a light "new messages" nudge.

Users want to search their chat history.

With end-to-end encryption, search runs on the device over its local copy. Without it, a per-user search index fed from the message stream.

Edit and delete messages.

Both are new events in the conversation ("edit seq 1043", "delete seq 1043"), delivered like messages, so every device applies them in order. Storage keeps the latest version (or a tombstone).

Multi-region.

Users connect to their nearest region. Each conversation is homed in one region, which assigns its sequence numbers; members elsewhere receive through cross-region replication with a little extra latency. Home a conversation near most of its members.

How do you fight spam without reading messages?

Metadata and behaviour: account age, sending rate, how many recipients block or report, links shared (where users report them). Rate limits on new accounts and on messages to non-contacts.

Why not long polling?

Long polling works but costs a request per message batch and more battery. WebSockets keep one connection open for both directions. Push notifications cover the app being closed.

Variation: community servers with channels

Another common form: "Design chat for communities, like Discord: servers with many channels, some with hundreds of thousands of members." One-to-one delivery still works, but the unit changes from a conversation to a channel inside a server, and most members of a big server are offline or looking at another channel.

Read more: Discord's engineering blog on storing messages by channel and time bucket and on one process per server.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
Gateway diesA few seconds of "connecting…"Connection count dropReconnect with jitter; sync by sequence
Pub/sub shard downMessages arrive late for some usersPublish errorsRecipients' next sync fetches from storage; replicas take over
Message store slowSingle tick takes longerWrite latencySender retries with the same client_msg_id; no duplicates
Push provider outageOffline users not wokenPush error rateMessages are stored; delivered on next app open
Lost ack on a bad networkNothing—Resend is deduplicated by client_msg_id

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch:

At 10×:

A 45-minute interview

0–4Frame the problem (groups, offline, history, encryption)
4–9Must do, how well, numbers
9–13Nouns and contract
13–21First working version, parked list
21–40Deep dives: ordering (9.1), routing (9.2), sync (9.3), storage (9.4)
40–45Follow-ups and wrap-up

A 60-minute interview

0–5Frame the problem
5–11Must do, how well, numbers
11–16Nouns and contract
16–25First working version
25–52Deep dives 9.1–9.7 and the final picture
52–60Follow-ups: gateway failure, channels, regions; wrap-up
LevelWhat a strong answer shows
Mid-levelWebSockets, a chat service, a message store, push for offline users, basic receipts.
SeniorRouting between gateways via pub/sub, per-conversation sequence numbers and idempotent sends, sync after reconnect, storage partitioning, group fan-out, presence with TTLs.
Staff+Multi-device and end-to-end encryption implications, very large groups, multi-region homing, reconnect storms, retention as a cost driver, abuse handling without reading content.

Common mistakes:

13. Try it, check yourself, read more

Send messages between two phones, take one offline, and watch the ticks: ✓ stored on the server, ✓✓ delivered to the other phone, ✓✓ read.

Asha

Ravi

What gives messages a reliable order?

One ordered stream per conversation, numbered as messages are stored.

How does a phone catch up after being offline?

Pub/sub only reaches current subscribers; storage plus sequence numbers make catch-up exact.

Why keep WebSocket gateways thin?

Connection servers rarely change; business logic in separate services deploys freely.

The sender's ack is lost and the phone resends. Why isn't the message stored twice?

Idempotent sends: same client id, same message.
ResourceWhy
How Discord stores trillions of messagesMessage storage partitioning in practice.
Signal protocol specificationsHow end-to-end encryption works for one-to-one and group messaging.
Design WhatsApp (Hello Interview)Another walkthrough.