Interview guides
Chat and Messaging System Design Interview: Presence, Ordering, Fan-Out, and Offline Delivery
Learn chat system design for interviews: model durable messages, order conversations, manage presence, scale fan-out, and recover offline clients safely.
A strong chat and messaging system design answer separates durable truth from fast delivery. Persist an idempotent message first, assign it a per-conversation sequence, then fan it out to online devices while preserving a cursor-based path for every offline device to catch up. Presence and sockets are useful soft state; neither should decide whether a message exists.
This guide shows software engineers how to reason about that contract in an interview.
The goal is not to reproduce WhatsApp or Slack. It is to define a defensible product scope, trace one message end to end, and explain where each user-visible guarantee lives.
Chat system design: the quick answer
Design around five stages: Connect → Commit → Sequence → Fan Out → Reconcile. Clients maintain authenticated real-time connections through a gateway fleet. A send API checks membership and a client-generated idempotency key, commits the message to authoritative storage, and assigns a position within its conversation. Only then does an event drive online delivery, unread state, receipts, and push notifications.
Partition message history by conversation because the dominant reads are “load this conversation” and “continue after sequence N.” Promise order inside one conversation, not across unrelated chats. Use at-least-once delivery with stable message IDs and client deduplication. A disconnected device resumes from durable per-device cursors; a hot group switches away from per-member write amplification.
Clarify the product contract before drawing boxes
Ask whether the system needs one-to-one chats, groups, channels, multiple devices, edits, deletes, attachments, presence, typing indicators, receipts, search, and end-to-end encryption. Then establish scale: peak sends per second, concurrent connections, retention, maximum group size, and regional latency goals.
Make the guarantees explicit. A practical interview contract is:
| Concern | Defensible promise | Important non-promise |
|---|---|---|
| Acceptance | Acknowledged messages are durably recoverable | A socket write alone is not acceptance |
| Ordering | One server order per conversation | No global order across conversations |
| Delivery | Low-latency online delivery with retry and deduplication | No exactly-once network transport |
| Offline use | Cursor-based catch-up without permanent gaps | Push is not authoritative history |
| Presence | Recent best-effort status with expiry | “Online” is not proof a user saw a message |
Voice/video calls and media transcoding can remain out of scope. State those boundaries so you can spend the interview on the four hard parts named in the prompt.
Model conversations, messages, memberships, and device progress
Use conversation, membership, message, device_cursor, and receipt as the core records. A message needs message_id, conversation_id, sender_id, client_message_id, sequence, content_or_ciphertext, and timestamps. The unique key (sender_id, client_message_id) makes a retried send return the original result instead of creating a duplicate.
Keep conversation metadata and membership separate from the append-oriented message store. A representative API surface is:
POST /conversations/{id}/messageswithclient_message_id.GET /conversations/{id}/messages?after_sequence=N&limit=K.GET /sync?cursor=Tto discover changed conversations and receipt state.- A WebSocket event stream for messages, presence, typing, and receipt updates.
Authorization belongs on every send and history read. Membership changes need a defined effective sequence so a removed user cannot fetch later messages and a newly added user receives only the history allowed by product policy.
Use Connect → Commit → Sequence → Fan Out → Reconcile
This framework keeps the interview narrative causal. Connect authenticates a device and registers which gateway currently owns its socket. Commit validates membership, deduplicates the client operation, and stores the message durably. Sequence assigns a monotonically increasing position within the conversation.
Fan Out publishes the committed event to online gateways and any inbox or unread-count pipeline. Reconcile lets devices resume from durable cursors, detect a sequence gap, fetch missing history, and advance delivery or read state idempotently.
The sequence is load-bearing: publishing before the durable commit can acknowledge data that disappears after a broker or process failure. A transactional outbox, change-data capture stream, or storage engine that atomically appends both message and event intent prevents “stored but never published” from becoming a permanent gap.
Treat presence as expiring soft state
The WebSocket protocol supplies bidirectional framed communication, but the application still owns authentication, heartbeats, reconnects, backpressure, and event semantics. Gateways should be horizontally scalable; a routing index maps each user and device to the gateway holding its current connection.
Presence is deliberately weaker than message history. Store online, unavailable, or offline with a lease expiry and last-active time. Refresh it from heartbeats or meaningful activity, expire it after disconnect uncertainty, and fan it only to authorized contacts or shared groups. The Matrix presence model is a useful public example of separating state, recent activity, and idle status.
Do not write every heartbeat into the durable message database. A regional cache or presence service can lose a lease and recover without losing conversations. Typing indicators should use the same ephemeral path with aggressive expiry.
Guarantee conversation order without claiming exactly once
Client timestamps cannot define order: clocks drift, devices reconnect, and two senders can write concurrently. Instead, route one conversation to an ordering owner or allocate its next sequence with a conditional update. Persist the server sequence with the message, then use it for pagination, gap detection, edits, deletes, and receipts.
The trade-off is a hot partition. Batching sequence allocation improves throughput but can leave unused numbers; that is acceptable if the contract promises increasing order rather than gapless integers. Sharding one conversation across independent writers removes the bottleneck only by weakening or coordinating the ordering guarantee.
Network retries still create duplicate deliveries. Deduplicate sends at the server with client_message_id, deduplicate events on each device with message_id, and make cursor advances monotonic. “Exactly once” is better described as effectively-once display built on idempotent processing, not a magical transport mode.
Choose fan-out by conversation size and activity
For a direct chat or small group, fan-out-on-write is reasonable: after commit, identify connected recipient devices, push the event to their gateways, and update compact inbox or unread state. This gives fast opening and simple badge counts at manageable write cost.
For a 100,000-member channel, writing one row per member per message causes extreme amplification. Store one authoritative conversation event, deliver immediately only to active subscribers, and let inactive members pull history on read. Cache recent pages and maintain coarse changed-conversation indexes rather than duplicating full messages into every inbox.
A hybrid threshold should depend on membership, online ratio, send rate, and read behavior—not a hard-coded brand-name rule. If a group becomes hot, isolate its sequencer, batch gateway delivery, cap per-connection buffers, shed optional signals such as typing events, and protect durable acceptance from slow consumers.
Make offline delivery a reconciliation protocol
A device may disconnect after the message commits but before its socket event arrives. On reconnect, it submits a durable cursor, receives the conversations that changed, and pulls each conversation after its last sequence. If it observes sequence 104 after 102, it pauses display or marks a gap while fetching 103.
The Matrix /sync API publicly demonstrates the snapshot-plus-incremental-delta pattern with continuation tokens. Your design can use a different protocol while preserving the same principle: a socket accelerates delivery, while a cursor proves catch-up progress.
Track receipt states separately. accepted means durable server commit; delivered should name a device acknowledgement; read is an explicit user/device cursor. Push notifications wake or inform an offline device, but they do not replace history sync. Firebase distinguishes messages enqueued for delivery from messages received, displayed, or opened in its delivery documentation.
For the provider-specific retry, preference, and quiet-hours path, link the notification system design guide instead of rebuilding that system inside chat.
Handle regional failure, privacy, and encryption boundaries
Choose a home region per conversation if strict ordering is more important than accepting writes in every region. A remote client can connect locally, then route the send to the conversation owner. During an owner-region outage, either fail writes, queue them without claiming acceptance, or explicitly relax order; name the choice.
Reconciliation jobs should compare authoritative messages with publish markers, changed-conversation indexes, and device cursors. Monitor commit latency, publish lag, online delivery latency, reconnect gap rate, duplicate suppression, hot-partition load, presence churn, and authorization failures separately.
Encrypt transport and stored data by default, but treat end-to-end encryption as a different product contract. With E2EE, servers route ciphertext and manage device/key metadata while server-side search, moderation, previews, and backup recovery change substantially. The Signal protocol specifications illustrate why messaging encryption is composed from distinct cryptographic protocols rather than added as one storage flag.
A 45-minute interview walkthrough
| Time | What to do | Evidence of a strong answer |
|---|---|---|
| 0–5 min | Clarify features, scale, order, retention, and encryption | Explicit scope and non-goals |
| 5–12 min | Define entities, APIs, and guarantees | Idempotency key, sequence, and cursor |
| 12–22 min | Trace send, commit, publish, and live delivery | Durable acceptance before fan-out |
| 22–32 min | Deep-dive ordering, group size, and offline sync | Hybrid fan-out and gap repair |
| 32–40 min | Break the design with hot groups and region failure | Backpressure, ownership, reconciliation |
| 40–45 min | Summarize trade-offs and metrics | Clear promises and remaining risks |
Narrate one message from a sender on device A to an offline recipient on device B. That single trace exposes whether your architecture actually connects durability, order, delivery, and recovery.
Frequently asked questions
Should a chat system use WebSockets or long polling?
WebSockets fit low-latency bidirectional delivery and presence updates, while long polling can be a simpler fallback. Neither transport is the source of truth. Persist messages before acknowledgement and provide cursor-based history reads so a reconnecting client can recover regardless of how its live connection failed.
How do you guarantee message ordering in a distributed chat system?
Define the scope as one conversation, then give that conversation one ordering owner or a coordinated conditional sequence allocator. Store the assigned sequence with the message and route retries through idempotency. Global ordering is usually unnecessary and would add coordination without improving the user experience.
Is exactly-once message delivery possible?
Networks can lose acknowledgements and repeat requests, so exactly-once transport is not a practical end-to-end promise. Use at-least-once delivery, stable message IDs, idempotent server writes, device-side deduplication, and monotonic cursors to produce effectively-once display and reliable gap recovery.
How should presence work at scale?
Presence should be leased, expiring soft state stored separately from durable messages. Gateways refresh device sessions; a presence service aggregates them into user status and distributes updates only to authorized interested users. Throttle changes and let stale entries expire rather than writing every heartbeat permanently.
What changes when end-to-end encryption is required?
The server stores and routes ciphertext, while clients manage identity keys, sessions, and device changes. Server-side search, content moderation, notification previews, multi-device history, and backup recovery need new privacy-preserving designs. Clarify this boundary early instead of claiming that encryption at rest provides E2EE.
Final takeaway
For a chat and messaging system design interview, keep durable history at the center: Connect → Commit → Sequence → Fan Out → Reconcile. If you can defend the acceptance boundary, conversation order, hybrid group delivery, and offline cursor protocol under failure, you have a coherent design rather than a collection of services.
Sources and Further Reading
- IETF RFC 6455: The WebSocket Protocol
- Matrix Client-Server API: incremental synchronization
- Matrix Client-Server API: presence
- Firebase Cloud Messaging: understanding message delivery
- Signal protocol documentation
Research note: This guide was checked on September 1, 2026. Standards and vendor behavior can change, and the constraints given in your own interview remain authoritative.