Chapter 20 — Design a Chat App (WhatsApp)

Chapter 20 — Design a Chat App (WhatsApp)

Hey everyone! Welcome back to Namaste System Design! 🙏

Chat is a beautiful design problem because it flips a core assumption: so far, the client always started the conversation (request → response). But in chat, the server must push a message to you the instant your friend sends it — no refresh, no polling. That needs a different kind of connection. Let's design WhatsApp.


Step 1 — Clarify Requirements

FUNCTIONAL:
  ✔ 1-to-1 messaging (real-time delivery)
  ✔ Online/last-seen presence
  ✔ Delivery receipts (sent ✓ / delivered ✓✓ / read ✓✓ blue)
  ✔ Message history (persist), offline delivery
  ✔ (stretch) group chat

NON-FUNCTIONAL:
  ✔ Real-time, low latency (feels instant)
  ✔ Highly available & reliable — NEVER lose a message
  ✔ Consistent ordering within a conversation

SCOPE: "Focus on 1-to-1 real-time messaging + offline delivery."

Step 2 — The Key Insight: You Need a Persistent Connection

Normal HTTP is request → response: the client asks, the server answers, done. But the server can't start a message. How does it push to you? Let's compare the options:

TechniqueHowVerdict for chat
PollingClient asks "any new msgs?" every few sec❌ Wasteful, laggy
Long pollingClient asks, server holds until a msg arrives⚠️ Better, still clunky
WebSocketsONE persistent 2-way connection stays open✅ The right tool
   WEBSOCKET = a permanent, two-way pipe between client and
   server. Either side can send anytime. Opened once, kept alive.

   [ Phone ] ══════ persistent WebSocket ══════ [ Chat server ]
        ▲                                              │
        └──── server PUSHES new message instantly ─────┘

Step 3 — High-Level Design

   [ Users ] ══ WebSocket ══ [ Chat Servers ]
                                   │
                     ┌─────────────┼──────────────┐
                     ▼             ▼              ▼
              [ Presence /   [ Message      [ Message
                Session DB ]   Queue ]         Store DB ]
             "who's online &  routes msgs    (history, e.g.
              on which server" between users   Cassandra)

   Each connected user holds an open WebSocket to SOME chat
   server. We must track WHICH server, so we can route a
   message to the right connection.
LET'S TRACE A MESSAGE:  Aisha → Bob
──────────────────────────────────
  1. Aisha's phone sends msg over her WebSocket to Chat Server 1
  2. Server persists the message to the Message Store (durability!)
  3. Look up Bob in the session store: "Bob is on Chat Server 5"
  4. Route the message (via queue) to Chat Server 5
  5. Server 5 PUSHES it down Bob's open WebSocket → instant ✅
  6. Bob's app ACKs → Aisha sees "delivered ✓✓"

Step 4 — Deep Dive: Routing & Offline Delivery

(a) The connection-server registry

   With millions of users across thousands of chat servers,
   how does Server 1 find Bob? A fast lookup:

   session store (Redis):  "bob → chat-server-5"

   When Bob connects, we record his server. When routing a
   message to Bob, we check this map and forward to server 5.

(b) What if Bob is OFFLINE?

   No open WebSocket for Bob → we can't push.
   → Store the message as UNDELIVERED in the Message Store.
   → When Bob reconnects, his app pulls all pending messages.
   → THEN mark delivered. This is why we PERSIST before
     delivering — so a message is NEVER lost. 🔒

(c) Message ordering

   Within one conversation, messages must appear in order.
   Attach a sequence number or timestamp per conversation and
   sort by it. (Global ordering across all chats isn't needed.)

Step 5 — Presence (Online / Last Seen)

   ONLINE  = has an active WebSocket connection.
   When Bob connects   → mark "online" in the presence store.
   When Bob disconnects→ mark "last seen 

Step 6 — Bottlenecks & Trade-offs

  ⚠ Millions of open      → WebSockets are stateful; use many
    connections             chat servers + a connection LB.
                            One server holds ~100k+ sockets.
  ⚠ Durability            → PERSIST every message before ACK;
                            never lose a message.
  ⚠ Group chat            → fan-out (like Ch 19) to each member's
                            connection; big groups need care.
  ⚠ Server crash          → clients auto-reconnect to another
                            server; session store updated.
  ⚠ Security              → end-to-end encryption (client-side);
                            server only routes ciphertext.
  ⚠ Ordering              → per-conversation sequence numbers.

Interview Questions — Quick Fire!

Q: Why use WebSockets instead of HTTP polling for chat?

"Normal HTTP is request-response — the server can't initiate a message, so with polling the client must repeatedly ask for updates, which is wasteful and laggy. A WebSocket is a single persistent, bidirectional connection kept open between client and server, so the server can push a message the instant it arrives. That gives real-time delivery with far less overhead than polling."

Q: How does a message get from sender to receiver?

"The sender's message travels over their open WebSocket to a chat server, which first persists it for durability. Then it looks up which server the recipient is connected to via a session store, routes the message there, and that server pushes it down the recipient's WebSocket. The recipient acknowledges, which lets the sender show a delivered receipt."

Q: How do you handle messages when the recipient is offline?

"If the recipient has no active connection, we can't push. Because we persist every message before attempting delivery, the message is safely stored as undelivered. When the recipient reconnects, their app pulls all pending messages and we mark them delivered. Persisting first guarantees a message is never lost even if the recipient is offline."

Q: How do you track which server a user is connected to?

"With a fast session registry, typically in Redis, mapping each user to the chat server holding their WebSocket. When a user connects, we record their server; to route a message, we look up the recipient's server and forward it there. When they disconnect or reconnect elsewhere, the mapping is updated."

Q: How does online presence work?

"A user is online while they have an active WebSocket. On connect we mark them online; on disconnect we record last-seen. Clients send periodic heartbeats, and if several are missed we treat the user as offline, which avoids ghost-online states after a crash. At scale, presence changes are high-volume, so they're propagated with eventual consistency and pub/sub to only the user's contacts."


Key Points to Remember

ConceptKey Takeaway
Key insightServer must push → use WebSockets (persistent 2-way connection), not polling.
RoutingSession store maps user → their chat server, so messages reach the right socket.
DurabilityPersist every message BEFORE delivery → never lose a message.
OfflineStore as undelivered; recipient pulls on reconnect.
PresenceOnline = active connection; heartbeats detect drops; eventual consistency at scale.

What's Next?

Chapter 21 enters the world of maps and real-time location: Design a Ride-Sharing App (Uber). How do you match a rider to the nearest driver among millions, updating locations every few seconds? Geospatial indexing awaits.

Keep designing, keep scaling! See you in the next one!