Hirestack
Hirestack
Worked Example 15.14 · 14 of 15

Design Slack

📖 3 min read · 8 sections · May 2026
Asked at: Slack Microsoft Teams Discord

The setup

"Design Slack. Focus on the channel-based messaging core: users in channels exchange messages in real time; channels can have thousands of members; the message history is searchable. 10M concurrent users; 100K messages/sec peak."

What's different from WhatsApp: WhatsApp is 1:1 (or small groups). Slack is many-to-many through channels. Each channel has potentially thousands of members. Posting a message must fan out to every member's connected device.

Clarifying questions

QuestionStory answer
Max channel size?Up to 100K members (busy companies have huge #general channels).
Message edit/delete?Yes — updates propagate.
Threading?Yes — but treat threads as separate channels for design.
Search?Mention briefly; full-text search across all messages user has access to.
Mobile + desktop?Yes; mobile may be intermittent.

Design Decision 1: channel-based pub/sub

When user A posts in #general (5,000 members), the message must reach 5,000 connected clients. Naive: server iterates members and sends individually → 5,000 messages per post → at 100K posts/sec, 500M outbound messages/sec.

Better: per-channel pub/sub. Each WSS client subscribes to topics for the channels they're in. Posting publishes once; the pub/sub layer fanout-delivers to subscribers.

Client (subscribed to: #general, #engineering, #random) ─┐
                                                          │
                                          [WSS Gateway]   │
                                                          │
                                       subscribed to topics: #general, #engineering, #random
                                                          │
                                                          ↓
                                                 [Pub/Sub layer]
                                                  ↓     ↓     ↓
                                            #general #engineering #random

When a message arrives in #general:

  1. Posted via API to Message Service
  2. Persisted to Cassandra (channel history)
  3. Published to pub/sub channel "#general"
  4. Each gateway with at least one subscriber to "#general" receives the message
  5. Gateway forwards to each subscribed client via their WSS

This is fundamental to many-to-many messaging. Pub/sub absorbs the fan-out.

Design Decision 2: the Slack Flannel pattern

Slack famously built "Flannel" — a per-datacentre service that caches all channel/user/team metadata at the edge.

The problem Flannel solves: every UI interaction needs context (what channels am I in? who's in this channel? what are my permissions?). Naively, each interaction queries backend databases — slow under load.

Flannel: each datacentre holds a complete in-memory picture of the company's metadata (channels, members, permissions). Clients query Flannel, not the backend, for the bulk of UI needs. Flannel synchronises with backend asynchronously.

Architectural lesson: when you have richly-relational metadata accessed on the hot path, a dedicated metadata cache tier is enormously valuable.

Design Decision 3: message storage

Channel messages partitioned by (channel_id, time_bucket) in Cassandra. Within a partition, messages clustered by timestamp. Same pattern as WhatsApp; channel_id replaces conversation_id.

For search: messages indexed in Elasticsearch (or similar). User-facing search APIs query the search index, not Cassandra. Permission filtering applied at query time (you only see messages from channels you're a member of).

Cross-examination round 1: presence at scale

Interviewer: Slack shows "John is online" for each member of every channel you're in. If you're in 50 channels with avg 100 members, that's 5,000 presences to track. Don't push updates for all.
Presence updates are not pushed for every user-channel pair — combinatorial nightmare. Solution: each user has a "presence record" updated by their WSS connection (heartbeat). Client fetches presence for visible members (current channel's members) lazily via API. Pub/sub for presence changes is scoped to user's contacts/recent-conversations, not all channels. Slack's approach: presence tracked at user level, not channel level; UI fetches as needed.

Cross-examination round 2: thread reply notifications

Interviewer: User A posts in #general. User B replies in a thread. Who gets notified?
Thread participants only. Channel members who haven't engaged in the thread don't get pinged. Mention notifications (@user) bypass thread membership — directly notify the mentioned user. Per-user notification preferences govern: do they want all channel messages, only mentions, or none? Stored in user_preferences; queried at delivery time.

Takeaway

Slack-class messaging is fundamentally pub/sub with rich metadata caching. Pub/sub handles fanout to thousands of concurrent connections per channel. The metadata cache (Flannel) ensures every interaction has fast access to "who can see what" without backend hits. The general lesson: any system with rich relational metadata accessed on the hot path benefits enormously from a metadata caching tier.

The deeper insight: permission and visibility computation is a hot path that's often overlooked. "Show me this channel" requires knowing "is this user a member?" — which requires permission data. At Slack scale, this metadata is queried millions of times per second. Caching it at the edge is essential.