Skip to content
Laviyx Learn

Try

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.

Advanced5 min readUpdated
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#

Python
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}")
Output
Messages: 231,481 per second on average
Storage: 4 TB a day
Gateways for 100,000,000 live connections: 1,000

The 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#

Text
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#

  1. Alice sends a message with a unique client ID.
  2. The server saves it, and only then acknowledges it, which gives Alice one tick.
  3. It looks up Bob's gateway. If Bob is online, the message goes straight to him.
  4. Bob's device confirms, and Alice sees two ticks.
  5. 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:

Python
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"])])
Output
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:

Text
messages
  partition key: chat_id
  sort key:      seq (newest first)
  fields:        sender_id, body, created_at, status

Partitioning 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:

Python
for members in (5, 100, 5_000):
    print(f"Group of {members:>5,}: one message means {members - 1:,} deliveries")
Output
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 deliveries

For 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?

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.