System DesignExpert

Design chat with presence

“Design real-time one-to-one and group chat with online/offline presence. Cover connections, message routing, ordering, delivery receipts, presence at scale, and offline users.”

What this tests

  • Stateful WebSocket connection servers and how to route to the right one
  • Per-conversation ordering and storage; acks and receipts
  • Presence as a heartbeat/TTL problem and the fan-out cost of status changes
  • Group fan-out and offline push as the scaling problems

Answers by level

Read the beginner answer first and notice what is missing.

Clarify: 10 M daily users, 1 M concurrent connections, average 40 messages per user per day ≈ 4,600 msg/s, group size limits (say 500), delivery target < 500 ms. WebSockets are stateful, so the connection tier is the one part that cannot be stateless: each connection server holds ~100k sockets, and a session registry in Redis maps user_id → connection_server so the message router knows where to push. Sending: client → any API → persist to a message store keyed by (conversation_id, sequence) → route to the recipient's connection server via a pub/sub channel per server → push. If the recipient is not connected, enqueue a push notification.

Ordering is per conversation: a monotonically increasing sequence assigned by the store for that conversation (a per-conversation counter or a single-writer partition keyed by conversation id, see Kafka-Style Logs: Topics, Partitions, Offsets) — clients render by sequence, and gaps trigger a fetch. Receipts are just small messages back through the same path (delivered, read with the last sequence seen). Presence: a client heartbeats every 30 s; the server sets presence:{user} = online in Redis with a 60 s TTL; going offline is the TTL expiring, not a disconnect event, because disconnects are often not observed.

Green flags · Red flags

Strong green flag · Computes the presence fan-out cost for a user with many contacts and switches to subscription-based, batched presence unprompted.
Green flags
  • Estimates concurrent connections, msg/s and connections per server
  • Session registry mapping user → connection server, with pub/sub routing
  • Per-conversation sequence for ordering; store is the source of truth; resend on reconnect
  • Presence via heartbeat and TTL, and identifies presence fan-out as the expensive part
  • Graceful drain and jittered reconnect for deploys
  • Offline path via push notifications
Red flags
  • "WebSockets behind a load balancer, plus Kafka for scale." — with no answer for which server holds the recipient's socket
  • Presence as a database flag cleared on disconnect
  • Relies on the socket, not the store, for delivery
  • Orders messages by client timestamp
  • No plan for what a deploy does to a million open connections

Follow-up questions

F1
User B is connected to server 7. How does server 3 deliver A's message?
F2
Why heartbeat + TTL for presence instead of disconnect events?
F3
A 2,000-member group sends 50 messages a minute. Fan-out on send still?

Scenario

A candidate proposes: WebSocket servers behind a round-robin load balancer, messages written to PostgreSQL ordered by created_at, presence stored as is_online in the users table, and Kafka "to scale". Ask them: when Alice sends to Bob, which server pushes to Bob and how does it know? What happens to ordering when two messages arrive within the same millisecond from different servers? What does the presence flag show after Bob's phone loses signal on a train? Guide them to the design and note where they jump to infrastructure before answering the question.

Learn this topic