Group chat and channel fan-out
Delivering messages to groups of every size: small groups versus huge channels, fan-out on write to inboxes versus reading from a channel log, membership storage, per-member read state and unread counts, read receipts, mentions, large group limits and notification throttling.
Reading is half of it. See this used in a real interview: walk through Design WhatsApp →
A one-to-one message goes to one person. A group message goes to every member, on every device, with per-member unread counts, read receipts and notifications. Group sizes range from three friends to a 100,000-member community channel, and the right delivery strategy is different at each end. Chat interview questions almost always include groups, and this is where the fan-out decision lives.
Two delivery models
Fan-out to inboxes (write time): when a message is sent, write a reference into each member's inbox (or per-member sync log), then push to their connected devices.
- Reads are simple: each user syncs their own inbox.
- Writes multiply by group size.
- Natural for small groups and for E2EE designs that encrypt per device.
Channel log (read time): store the message once in the channel's ordered log; members read from the channels they belong to, tracking how far they have read.
- One write per message regardless of size.
- Reads must merge several channels (the client syncs each channel it is in).
- Natural for large channels and workspace chat.
Most products use inbox fan-out for small groups and channel logs for large ones, or channel logs everywhere with per-user state on top. See fan-out on write vs read.
Real-time delivery
Regardless of storage, connected members should receive the message within a second:
- The message service publishes to the channel's topic; gateways holding members' connections are subscribed (or are looked up via a session registry) and push to the devices. See presence and connection management.
- Offline members get push notifications (subject to settings) and catch up from storage when they reconnect, using sequence numbers. See message ordering and sequence numbers.
- For huge channels, avoid pushing every message to every member's device; push only to members actively viewing the channel, and update others' unread indicators lazily.
Membership
- Store membership both ways: channel to members (for fan-out) and user to channels (for the sidebar and sync). Keep them consistent with transactions or events.
- Membership changes are messages in the channel log too ("Alex joined"), so history makes sense.
- Large membership lists are paged and cached; fan-out jobs read them in batches. See graph data.
Read state and unread counts
Per member, per channel, store a last read sequence number. Then:
- Unread count = latest sequence in the channel minus the member's last read (adjusted for messages they sent).
- Mentions are tracked separately (a per-user mention list or counter) because they matter more than general unread.
- Sync read state across the user's devices through the server.
This avoids per-message, per-member read rows, which would explode for large channels. See counting at scale.
Read receipts
"Seen by 3" in small groups needs per-member read positions, which the scheme above already has: a message is read by members whose last read is at or after it. Large channels typically drop read receipts (they are expensive and noisy) or show only counts.
Mentions and notifications
- Parse mentions at send time and notify those members with higher priority.
@channelor@everyonein a 10,000-member channel is a notification storm: restrict who can use it, rate-limit it, and batch notifications. See push notifications.- Respect per-channel mute settings and per-user notification preferences.
Size limits and tiers
Products set limits partly for system reasons: small groups (up to a few hundred or a thousand members) with full features (receipts, per-device encryption), and broadcast channels or communities with reduced features for larger audiences. Encrypted groups use sender keys so one ciphertext serves everyone. See end-to-end encryption.
Ordering and consistency
Each channel's messages get sequence numbers from the channel's owning partition, so every member sees the same order. Partition storage and processing by channel id; very active channels can become hot partitions, so give them dedicated capacity. See hot keys and skew.
Search and history
Channel logs are partitioned by (channel, time bucket) for history reads; search indexes messages with channel ids and checks membership at query time so users only find what they can see. See authorization and permissions and how Elasticsearch works.
In the interview
"Messages are stored once per channel with a per-channel sequence number; connected members get them via pub/sub to their gateways; offline members get pushes and sync by sequence on reconnect. Each member has a last-read sequence per channel, which gives unread counts and receipts without per-message rows. Small groups additionally fan out to per-user sync logs for fast multi-channel sync; huge channels skip receipts and only push to active viewers." See Design WhatsApp and Design Slack.
Checklist
- Inbox fan-out for small groups, channel logs for large ones.
- Real-time push to connected members; catch-up by sequence for others.
- Two-way membership storage kept consistent.
- Last-read sequence per member per channel for unread counts and receipts.
- Mentions tracked separately; channel-wide mentions throttled.
- Tiered features by group size; per-channel sequencing and partitioning.