Mustaque Nadim Academy
Part 5 · Case Studies

Design a Chat System

Adda adds real-time messaging: messages that arrive instantly, in order, even when a phone drops off the network. Deceptively deep — and it stretches everything the team learned.

The problem

Users have been asking for it for months: they want to talk to each other on Adda, not just post. So Ria greenlights direct messaging, and the team gathers at the whiteboard. The bar Fahim sets is simple to say and hard to build — he types "on my way", hits send, and it should land on his friend's phone before his thumb leaves the screen. His friend is on a train, so their phone flickers between towers, drops off the network for ten seconds, and reconnects — and the message is still there, in order, exactly once. That reliability is the product.

The naive version — the client polls the server every few seconds asking "any new messages?" — technically works and is technically terrible. It's slow (up to a full poll interval of delay), wasteful (millions of empty "nope" responses), and it can't feel instant. Real-time chat forces Adda to invert the flow: the server must push to the client.

Requirements

Functional

  • One-to-one messaging, delivered in near real time.
  • Message ordering within a conversation.
  • Delivery status: sent, delivered, read.
  • Online/offline presence.
  • Group chat and offline delivery.

Non-functional

  • Low latency (< 100 ms end to end when both are online).
  • Reliable: exactly-once delivery, no lost messages.
  • Highly available and horizontally scalable to hundreds of millions of connections.

A scale estimate

Shuvo sizes the new feature against Adda's user base:

  • 500 M daily users, 50 M concurrently connected.
  • 40 B messages/day → ~460 K messages/sec average, ~1 M/sec peak.
  • Each connection is a long-lived socket → 50 M open sockets. At ~65 K ports per IP and memory per connection, you need thousands of gateway servers just to hold connections open.
  • Messages are small (~200 bytes) but numerous — storage grows ~8 TB/day.

The insight

HTTP request/response is pull-shaped — the client asks, the server answers (the same HTTP model from Part 1). Chat needs the opposite: a persistent connection the server can push down at any moment. That's a WebSocket. Once you accept that every online user holds an open socket to some server, Nabila observes, the whole design reorganizes around one question: when A sends to B, how does A's server find B's socket?

How it works

Hold a persistent connection

Each client opens a WebSocket to a chat gateway server and keeps it open. The gateway tracks which users it holds sockets for. A heartbeat (ping every ~30 s) detects dead connections and drives presence.

Route a message to the right gateway

When A sends a message, A's gateway needs to reach B's gateway. A presence/session service maps user → gateway. A's gateway looks up B, then hands the message to B's gateway — often via an internal pub/sub bus, the fan-out pattern from Part 3, so gateways stay decoupled.

Persist before delivering

Write the message to durable storage first, then push. If B is online, B's gateway sends it down the socket instantly. If B is offline, the stored message waits and is delivered when B reconnects.

Confirm delivery

B's client sends an ack up its socket → the server marks the message delivered and notifies A. When B opens the chat, a read receipt flows the same way. These statuses are just more small messages riding the same pipes.

Message ordering

Within a single conversation, order must be stable. Don't trust client clocks. Assign each message a monotonic sequence number per conversation (from the conversation's storage partition or a sequencer). Clients render by sequence, not arrival time, so even out-of-order network delivery displays correctly. Across conversations, global order doesn't matter — which lets you shard by conversation, the same way Shuvo sharded Adda's data in Part 2.

Key decisions and trade-offs

WebSocket over polling or long-polling

Long-polling can fake push but wastes a connection per pending request and adds latency. A WebSocket is a single full-duplex connection that either side can write to at will — the natural fit. The cost: sockets are stateful, so your gateway layer is stateful, which complicates load balancing and deploys.

Stateful gateways change how you scale

Because a user is pinned to a specific gateway holding their socket, you can't blindly round-robin the way Tanvir's load balancer did in Part 1. You need the presence service to route to the right box, sticky-ish load balancing on connect, and graceful connection draining on deploy so you don't drop 50 M sockets at once — which is now Mou's problem to plan for.

Store messages in a store optimized for the access pattern — "fetch recent messages in a conversation, ordered" — which is a wide-column store (Cassandra-style) partitioned by conversation ID, clustered by sequence number.

Bottlenecks and how to scale

  • Connection capacity: add gateway servers; each holds a slice of the 50 M sockets. Presence service is the routing brain and must be fast and replicated.
  • Message fan-out in groups: a 1,000-member group turns one send into 1,000 deliveries. This is the same fan-out problem as Adda's feed — deliver to online members via their gateways, queue for offline ones.
  • Offline delivery: an inbox/queue per user holds undelivered messages; on reconnect the client syncs everything after its last-seen sequence number.
  • Exactly-once: clients attach a client-generated message ID; the server dedupes on it — the same idempotency-key idea from Part 3 — so a retry after a flaky network doesn't create a duplicate.

Practice

Recap

  • Chat inverts HTTP's pull model: hold a WebSocket and let the server push.
  • A presence/session service routes user → gateway; messages are persisted before delivery and queued when the recipient is offline.
  • Order with a per-conversation sequence number, dedupe with client message IDs, and treat group delivery as a fan-out problem.

In an interview

Start by killing polling — explain why the server must push, then introduce WebSockets. The moment you say "each user holds a socket to a gateway," ask yourself aloud "so how does one user's server reach another's?" — deriving the presence/routing service in front of the interviewer is exactly the reasoning they're testing. Save ordering, offline delivery, and exactly-once for the deep-dive.

How is this guide?

Last updated on

On this page