System Design course menu
System Design: Chat App Like WhatsApp
Design a chat application: WebSocket gateways, message delivery and ordering, storing billions of messages, read receipts, presence, group chats and offline delivery.
On this page
Chat is a favourite interview question, because messages must arrive quickly, in order, and never get lost, even when the receiver is offline.
1. Requirements#
Functional: one-to-one chat, group chat, online status, delivery and read receipts, and message history.
Non-functional: low latency, messages never lost, and very high availability.
2. Estimates#
daily_users = 500_000_000
messages_each = 40
bytes_each = 200
per_second = daily_users * messages_each / 86_400
per_day_tb = daily_users * messages_each * bytes_each / 1_000_000_000_000
print(f"Messages: {per_second:,.0f} per second on average")
print(f"Storage: {per_day_tb:,.0f} TB a day")
connections = daily_users * 0.2
per_server = 100_000
print(f"Gateways for {connections:,.0f} live connections: {connections / per_server:,.0f}")Messages: 231,481 per second on average
Storage: 4 TB a day
Gateways for 100,000,000 live connections: 1,000The system is write-heavy, and it holds millions of open connections.
3. Connections#
HTTP polling wastes resources and adds delay. Chat uses WebSockets, which keep a two-way connection open. See real-time options.
4. High-level design#
Alice --ws--> Gateway A --+ +--> Gateway B --ws--> Bob
+--> Message service ---+
| |
v v
Message DB Session store (Redis)
user --> gateway- Gateways hold WebSocket connections. They keep little state, apart from the connections.
- The session store records which gateway each user is on.
- The message service saves messages and routes them.
- Push notification services reach offline phones.
5. Sending a message#
- Alice sends a message with a unique client ID.
- The server saves it, and only then acknowledges it, which gives Alice one tick.
- It looks up Bob's gateway. If Bob is online, the message goes straight to him.
- Bob's device confirms, and Alice sees two ticks.
- If Bob is offline, the message waits, and a push notification goes out.
The client's ID makes retries safe: if Alice's phone resends, the server ignores the duplicate.
6. Ordering#
Messages can arrive out of order, and clocks across devices disagree. Each conversation therefore gets its own increasing sequence number, assigned by the server:
from collections import defaultdict
sequence = defaultdict(int)
def assign(chat, text):
sequence[chat] += 1
return {"chat": chat, "seq": sequence[chat], "text": text}
inbox = [assign("a-b", "Hi"), assign("a-b", "Are you free?"), assign("a-c", "Hello"), assign("a-b", "Call me")]
arrived = [inbox[1], inbox[3], inbox[0]]
print("As delivered:", [m["seq"] for m in arrived])
print("As shown: ", [m["text"] for m in sorted(arrived, key=lambda m: m["seq"])])As delivered: [2, 3, 1]
As shown: ['Hi', 'Are you free?', 'Call me']Sequence numbers also reveal gaps. If a device has 41 and 43, it asks for 42.
7. Storage#
Messages are written constantly, and read recent-first, by conversation. A wide-column store such as Cassandra or ScyllaDB fits well:
messages
partition key: chat_id
sort key: seq (newest first)
fields: sender_id, body, created_at, statusPartitioning by chat_id keeps a conversation together. User and group details can stay in a relational database.
To sync after being offline, a device sends the last sequence number it saw for each chat, and receives whatever is newer.
8. Group chat#
Sending to a group means delivering to each member. For small groups, the server copies the message to each online member. Very large groups are handled differently:
for members in (5, 100, 5_000):
print(f"Group of {members:>5,}: one message means {members - 1:,} deliveries")Group of 5: one message means 4 deliveries
Group of 100: one message means 99 deliveries
Group of 5,000: one message means 4,999 deliveriesFor large groups, a queue spreads the delivery work, so that sending stays fast. Many apps also limit group size.
9. Presence#
"Online" looks simple, but status changes often. Clients send a heartbeat every 30 seconds or so, updating a key with a short expiry in Redis. When the key expires, the user is offline. Do not push every change to everyone: send status only to people viewing that chat.
10. Media#
Upload files to object storage with a pre-signed URL, and send only a link in the message. Serve them through a CDN.
11. End-to-end encryption#
With end-to-end encryption, devices encrypt messages, and the server cannot read them. It affects the design: search and history must work on the device, and syncing a new device becomes harder.
Trade-offs to mention#
- Fan-out work against send speed, in groups.
- Keeping all history on servers, or only until it is delivered.
- Wide-column stores give scale, but simple queries only.
Key takeaway#
Next: Design a News Feed.
Finished this lesson?
Related lessons
Recommended System Design resources
- System Design Interview by Alex Xu
Step-by-step walkthroughs of common design questions, for interview preparation.
Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.