Ecommet
Case study

Design a Chat App

Design one-to-one and group messaging like WhatsApp or Slack: persistent connections, message delivery and ordering, storage per conversation, delivery and read receipts, presence, and offline users.

Intermediate 5 min read 3 quiz questions

Jump to the quiz

In a nutshell

  • Clients keep a WebSocket to a gateway; a routing layer knows which gateway each online user is connected to.
  • Store messages by conversation (partition key) and ordered by a per-conversation sequence or time-ordered ID.
  • Delivery is at-least-once with client-generated message IDs for deduplication; receipts are just more messages.
  • Offline users get push notifications and fetch missed messages from their last-seen position when they reconnect.

Requirements

  • Functional: one-to-one and group chats (up to a few hundred members); text messages; sent/delivered/read receipts; online presence; message history across devices.
  • Non-functional: messages delivered in well under a second when both users are online; no message lost once acknowledged; order preserved within a conversation.

Estimates

Assume 100 million daily users sending 40 messages a day: 4 billion messages a day, about 46,000 per second on average and several times that at peak. At 200 bytes per message, that is roughly 800 GB of new messages a day before replication, so storage must scale out. If 20 million users are connected at once and one gateway holds about 100,000 connections, that is around 200 gateway servers.

API

Frames over the WebSocket, plus a history call

Example
-> { "type": "send", "client_msg_id": "c-7f3a", "conversation": 551, "text": "hi" }
<- { "type": "ack", "client_msg_id": "c-7f3a", "seq": 1042 }
<- { "type": "message", "conversation": 551, "seq": 1043, "from": 9, "text": "hello" }
-> { "type": "read", "conversation": 551, "up_to_seq": 1043 }

GET /conversations/551/messages?after_seq=1000&limit=50

Data model

TableKeyNotes
messagespartition: conversation_id; clustering: seqWide-column store; one slice per conversation
conversationsconversation_idMembers, type, last seq
user_conversationsuser_idConversations with last-read seq per user (unread counts)
sessions (cache)user_id → gateway id(s)Which gateway each online device is connected to

High-level design

Sending a message between two online users

Sending a message between two online users Asha → Gateway 1: send Gateway 1 → Chat service Chat service → Messages: store + seq Chat service → Sessions: where is Ravi? Chat service → Gateway 2: deliver Gateway 2 → Ravi: push AshaGateway 1WebSocketsChat serviceMessagesby conversationSessionsRavi → gateway 2Gateway 2Ravisendstore + seqwhere is Ravi?deliverpush

All steps The chat service stores the message, then routes it to the gateway holding the recipient's connection.

Diagram as text
  1. Asha's phone sends the message over its open WebSocket.
  2. The chat service assigns the next sequence number in the conversation and stores it; then it acknowledges to Asha.
  3. It looks up which gateway Ravi is connected to.
  4. That gateway pushes the message down Ravi's connection. If Ravi is offline, a push notification is sent instead.

Deep dives

Ordering and duplicates

Assign a per-conversation sequence number when the message is stored (a counter in the conversation row, or a time-ordered ID); clients display by it, so everyone sees the same order. The client attaches its own client_msg_id; if it resends after a lost ack, the server recognises the ID and returns the original sequence instead of storing a duplicate.

Offline and multiple devices

Each device remembers the last sequence it has per conversation. On reconnect it asks for everything after that. This also syncs a user's laptop and phone. Push notifications (APNs, FCM) wake offline phones; they carry little or no content, and the app then fetches.

Groups

For small groups, fan out to each online member's gateway. For very large channels, deliver to online members only and let others fetch history when they open the channel, the same pull-on-read idea as celebrity feeds.

Presence

Gateways publish heartbeats; a user is online while their connection is alive. Presence changes are high-volume and low-value: send them only to users who currently have that contact on screen, and debounce flapping connections.

Bottlenecks and trade-offs

  • Gateway restarts drop many connections at once: clients reconnect with jittered backoff, and deploys drain gateways gradually.
  • Hot conversations (huge groups) concentrate writes on one partition: rate-limit posting, or split very large channels.
  • End-to-end encryption means servers store ciphertext only, which limits server-side search and moderation; key management happens on devices.

The last five minutes

Common mistakes

  • Ordering messages by client timestamps, which disagree between phones.
  • Polling over HTTP for new messages instead of persistent connections.
  • Storing messages keyed by message ID, so loading a conversation scatters across shards.
  • Not deduplicating resent messages after a lost acknowledgement.

Interview questions

Try answering out loud before you open each one.

How do you guarantee a message is not lost?

The sender's app keeps the message until it receives the server's ack, which is sent only after the message is durably stored (replicated). If no ack arrives it resends with the same client message ID, and the server deduplicates. Recipients fetch from their last sequence on reconnect, so a missed push does not lose anything.

Check yourself

1. What decides message order in a conversation?
Show the answer

Answer: A sequence number assigned by the server per conversation. One authority, one order for everyone.

2. What is the best partition key for messages?
Show the answer

Answer: conversation_id. Loading a conversation then reads one partition.

3. A phone resends a message after losing the ack. How are duplicates avoided?
Show the answer

Answer: The server recognises the client message ID and returns the original. Idempotency through a client-generated ID.

Further reading

Your notes

Saved in this browser as you type.

Finished this topic?

Mark it read to track your progress, then carry on with the next one.

Next topic