· software-engineers Editorial · Career  · 5 min read

Swe Interview System Design Chat Application

Design a scalable chat application for interviews: WebSocket fan-out, message ordering, presence, and delivery guarantees.

Swe Interview System Design Chat Application

“Design a chat application” (WhatsApp/Slack/Messenger-style) remains one of the most-asked system design prompts in 2026 mid-to-senior interview loops precisely because it forces a candidate to reason about real-time delivery, connection state at scale, and consistency tradeoffs simultaneously — there’s no single “correct” architecture, only defensible tradeoffs clearly reasoned through.

This article gives you the components interviewers expect, in the order they expect you to derive them.

Requirements Clarification (Do This First, Always)

Before drawing boxes, nail down scope, because “chat application” spans wildly different systems:

  • 1:1 vs group chat vs both? Group chat introduces fan-out complexity that changes the entire architecture.
  • Delivery guarantee needed? At-least-once (duplicates possible, simpler) vs exactly-once (harder, usually achieved via client-side dedup on a message ID, not true exactly-once delivery).
  • Message ordering requirement? Per-conversation ordering is almost always required; global ordering across conversations is almost never required and is a red flag if a candidate designs for it.
  • Online presence and typing indicators? These are separate, lower-consistency-requirement subsystems that shouldn’t share infrastructure with the core message-delivery path.
  • Scale target? A back-of-envelope number (e.g., 50M DAU, 40 messages/user/day) drives every subsequent capacity decision.

Skipping this step and jumping straight to “we’ll use WebSockets and Kafka” is the single most common reason strong-on-paper candidates score poorly — the interviewer can’t evaluate tradeoff reasoning if scope was never established.

Core Architecture Components

  1. Connection layer: persistent WebSocket (or, less commonly in 2026, HTTP/2 server push) connections between clients and a fleet of stateful “gateway” servers. Each gateway holds an in-memory map of which user IDs are connected to it.
  2. Session/presence registry: a fast key-value store (Redis is the default answer) mapping user_id -> gateway_server_id, so any backend service can find which gateway to push a message through, regardless of which gateway a given sender is connected to.
  3. Message persistence: messages are durably written to a database (commonly a wide-column store like Cassandra/ScyllaDB, partitioned by conversation ID) before being acknowledged to the sender — this ordering is critical for delivery guarantees.
  4. Fan-out service: for group chats, this service looks up all recipients’ gateway locations via the presence registry and pushes the message to each connected gateway; for offline recipients, it queues a push notification instead.
  5. Push notification fallback: APNs/FCM integration for offline users, which is a separate, best-effort delivery path from the core message system.

Comparison: Fan-Out Strategies

StrategyWrite CostRead CostBest ForWeakness
Fan-out on writeHigh (write to every recipient’s inbox at send time)Low (read is a simple per-user query)Small-to-medium groups, most 1:1 and group chatExpensive for huge groups (broadcast channels)
Fan-out on readLow (single write to conversation log)Higher (must merge/query across conversations at read time)Very large broadcast-style channels/communitiesHigher read latency, complicates unread-count tracking
HybridModerateModerateProduction systems (Slack, Discord use variants of this)More implementation complexity, two code paths to maintain

The strong interview answer names the crossover point explicitly: fan-out on write breaks down once a single conversation has enough members that a single message send would require writing to tens of thousands of per-user inboxes synchronously — at that scale (large channels/broadcast lists), fan-out on read (or a hybrid with async fan-out) becomes necessary.

Message Ordering and Delivery Guarantees

Per-conversation ordering is typically achieved by:

  • Assigning a monotonically increasing sequence number per conversation (not globally), often via a single partition/shard owning that conversation’s ID in the persistence layer.
  • Clients render messages sorted by this sequence number, not by wall-clock arrival time at the client, since network jitter can reorder delivery even when the server enforces write order.

At-least-once delivery (the practical default, since true exactly-once across a network is not achievable without idempotent processing) is implemented via:

  • Client-generated message IDs (UUID), allowing the server and other clients to deduplicate retried sends.
  • Server acknowledgment (ack) back to the sender only after durable persistence, with the client retrying on timeout — meaning duplicates are an expected, handled case, not a bug.

Handling Reconnection and Offline Sync

A frequently underspecified but heavily probed area: what happens when a client reconnects after being offline for minutes or hours?

  • The client sends its last known sequence number per conversation (or a global “last sync cursor”) on reconnect.
  • The server queries persisted messages after that cursor and replays them, rather than relying on any in-memory queued state (which would have been lost when the gateway connection dropped).
  • This is why persistence-before-ack (mentioned above) is load-bearing: without it, a message sent while a recipient’s gateway connection blipped could be lost entirely rather than recoverable on reconnect.

Presence and Typing Indicators: Deliberately Lower Guarantees

A common interview trap is over-engineering presence. Correct framing: presence/typing indicators are best-effort, eventually-consistent, and explicitly allowed to be stale — using them as a design excuse to introduce strong consistency machinery (distributed locks, consensus) is a negative signal, not a positive one. A simple TTL-based Redis key (user:123:online, refreshed via heartbeat, expiring after ~30s of no heartbeat) is the expected answer, not a bespoke consensus protocol.

FAQ

Q: Should I use WebSockets or long-polling for the connection layer? A: WebSockets are the standard 2026 answer for bidirectional, low-latency chat — long-polling is worth mentioning only as a fallback for clients/networks that block persistent connections (some corporate proxies), not as the primary design.

Q: How do you handle a user connected to multiple devices simultaneously? A: The presence registry maps to a set of gateway connections per user rather than a single one, and the fan-out service pushes to all active connections for that user ID — with read-receipt/sync-state handled per-device via the same last-sequence-number cursor mechanism used for reconnection.

Q: What database is actually used for message storage at scale, and why? A: Wide-column stores (Cassandra, ScyllaDB, or DynamoDB) partitioned by conversation ID are the common answer, because they offer high write throughput and horizontal scalability for the append-heavy, partition-friendly access pattern of chat messages, at the cost of weaker cross-partition query flexibility than a relational store — a tradeoff worth stating explicitly.

This exact prompt, along with worked answers for messaging, notification, and real-time system design questions, is covered in The 0-to-1 SWE Interview Playbook (https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20).

Back to Blog

Related Posts

View All Posts »