Compile Ready
All system design questions
System Design/Common Interview Questions

Slack

Design a team-messaging platform with channels, threads, presence, search, and integrations.

Advanced 60m interview 21m read High frequency Popularity 84
Amazon Microsoft Meta

Problem Statement

Design Slack, a team messaging platform where users belong to workspaces, join public and private channels, exchange direct messages, reply in threads, search message history, receive mention notifications, and see presence in real time.

At interview scale, assume millions of concurrent WebSocket connections, very large enterprises with hundreds of thousands of users, channels ranging from small project rooms to huge announcement channels, and strict requirements around tenant isolation, message retention, compliance exports, and eDiscovery. The core challenge is not just storing chat messages; it is combining durable per-channel history, ordered real-time delivery, unread state, search, presence, and notifications without letting any one hot channel or tenant dominate the system.

This differs from WhatsApp-style personal chat. Slack is workspace and channel oriented, optimized for searchable organizational knowledge, granular permissions, integrations, and compliance. The default design should prioritize reliable team communication, fast catch-up from history, and operational controls over global end-to-end encrypted personal messaging.

Business use case

Slack helps teams coordinate work across projects, incidents, departments, and external partners. Channels become a shared knowledge base where decisions, files, threads, and context are discoverable long after the real-time conversation ends.

Enterprises pay for secure collaboration, retention controls, legal holds, eDiscovery, identity integration, and uptime. The design must therefore support both consumer-like responsiveness and enterprise-grade governance.

Functional Requirements

  • Create and manage workspaces with users, roles, teams, and tenant-level settings.

  • Create public channels, private channels, direct messages, and group direct messages.

  • Send, edit, delete, and retrieve messages with attachments, reactions, and threaded replies.

  • Deliver new messages to online channel members in real time over WebSocket connections.

  • Maintain per-user read cursors, unread counts, mentions, and notification preferences per channel.

  • Expose user presence such as active, away, offline, and do-not-disturb status.

  • Search messages within a workspace while enforcing channel membership and retention policy.

  • Support enterprise retention, audit logs, compliance exports, legal hold, and eDiscovery workflows.

Non-Functional Requirements

Latency

Message send acknowledgment should complete in under 200ms p99 within a region after durable persistence. Real-time delivery to online members in normal-sized channels should target under 500ms p99, while search and history reads can tolerate higher latency.

Availability

Core messaging and message history should target 99.99 percent availability. Non-critical features such as search freshness, typing indicators, presence fanout, and push notifications should degrade without preventing message sends and reads.

Scalability

The system must handle millions of concurrent WebSocket connections, billions of messages per day, and channels with member counts ranging from two users to hundreds of thousands. Fanout, WebSocket gateways, storage partitions, and search indexing must all scale independently.

Ordering

Users expect a consistent order within each channel or thread. The design should assign a monotonic per-channel sequence after authorization and before fanout, while allowing different channels to progress independently.

Durability

Messages are business records. A sent message should not be acknowledged until it is durably stored and an outbox event can drive downstream fanout, indexing, and notifications. Backups, replication, and retention controls are mandatory.

Tenant isolation

Workspace data, permissions, encryption keys, search results, retention policies, and operational limits must be isolated by tenant. A bug or spike in one enterprise workspace must not expose or starve another tenant.

Compliance

Enterprise customers need audit logs, retention policies, legal holds, exports, and eDiscovery search. These requirements must be designed as first-class workflows rather than bolted onto best-effort chat logs.

Capacity Estimation

Assumptions

Assume 50M daily active users across many workspaces, 5M average concurrent WebSocket connections, 15M peak concurrent connections, and 1B messages per day. Average channel messages are delivered to 20 online or recently active members, but a small number of huge channels can have 100K or more members.

Assume each persisted message record averages 2 KB before replication and search indexing. The record includes workspace id, channel id, message id, channel sequence, sender, timestamps, thread root, text pointer, edit metadata, retention flags, and small attachment metadata. Large file bytes are stored separately.

Daily active users

50M users

Across free, paid, and enterprise workspaces

Peak WebSocket connections

15M concurrent

About 30 percent of daily users connected at the busiest point

Messages per day

1B messages

Includes channel messages, DMs, thread replies, and edits as events

Average message write QPS

11,600 writes per second

1B divided by 86,400 seconds

Peak message write QPS

116,000 writes per second

10x average peak across regions and tenants

Average real-time fanout

231,000 deliveries per second

11,600 messages per second times 20 online recipients

Peak real-time fanout

2.3M deliveries per second

10x average delivery rate before huge-channel spikes

Raw message storage

2 TB per day

1B messages times 2 KB per record

One-year replicated hot storage

2.2 PB

2 TB per day times 365 days times 3 replicas before indexes

Presence heartbeats

500,000 heartbeats per second

15M peak connections sending a heartbeat every 30 seconds

Read cursor writes

58,000 writes per second average

50M users updating about 100 channel cursors per day

Calculations

  • Message writes: 1B messages per day divided by 86,400 seconds is about 11,574 writes per second, rounded to 11,600. A 10x peak gives about 116,000 writes per second.
  • Real-time fanout: if each message has 20 online or recently active recipients, average delivery attempts are 11,600 times 20, or about 231,000 per second. Peak delivery attempts are about 2.3M per second before special handling for huge channels.
  • Storage: 1B messages times 2 KB is about 2 TB raw message data per day. For one year, 2 TB times 365 is about 730 TB raw. With 3 replicas, compaction overhead, indexes, and retention metadata, plan for multiple petabytes.
  • WebSocket state: 15M peak connections times roughly 8 KB of gateway connection state is about 120 GB of memory before runtime overhead, TLS buffers, and per-process overhead. This requires many gateway hosts and careful connection sharding.
  • Presence: 15M peak connections with one heartbeat every 30 seconds creates 500,000 heartbeat events per second. Gateways should aggregate and suppress unchanged presence updates.
  • Read cursors: 50M users times 100 channel cursor updates per day is 5B cursor updates per day. 5B divided by 86,400 is about 57,870 writes per second. Coalescing and idempotent upserts are important.
  • Huge channels: a 100K-member announcement channel cannot be treated like a 20-member project channel. The system should deliver to online members immediately, materialize history once, and compute unread state lazily where possible.

API Design

GET/api/v1/realtime/connect

Upgrades an authenticated HTTP request to a WebSocket session. The response includes a connection id and resume token so the client can reconnect and request missed events.

Response


HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket

{
  "connectionId": "conn_8f31",
  "resumeToken": "resume_72a9",
  "lastEventId": "evt_901234"
}
  • 101WebSocket upgrade accepted
  • 401Missing or invalid authentication
  • 403User is not allowed to access the workspace
  • 429Connection rate limit exceeded
POST/api/v1/workspaces/{workspaceId}/channels/{channelId}/messages

Sends a message to a channel, DM, or thread. The clientMessageId makes retries idempotent, and threadRootMessageId is present for threaded replies.

Request


{
  "clientMessageId": "client_9b7c",
  "text": "Deploy is complete in production.",
  "threadRootMessageId": "msg_1001",
  "mentions": ["U123", "group_oncall"],
  "attachments": [
    { "fileId": "file_44", "title": "release-notes.pdf" }
  ]
}

Response


{
  "workspaceId": "W123",
  "channelId": "C456",
  "messageId": "msg_2048",
  "channelSequence": 9823144,
  "createdAt": "2026-07-26T07:10:00Z",
  "deliveryState": "accepted"
}
  • 201Message persisted and accepted for fanout
  • 400Invalid message body or attachment reference
  • 401Authentication required
  • 403Sender is not a member of the channel
  • 409Duplicate clientMessageId already accepted
  • 429Workspace, user, or channel rate limit exceeded
GET/api/v1/workspaces/{workspaceId}/channels/{channelId}/messages

Reads channel history in reverse chronological or sequence order. The caller must be a channel member, and results are filtered by retention, deletion, and legal policy.

Request


beforeSequence=9823144&limit=50&includeThreadSummary=true

Response


{
  "messages": [
    {
      "messageId": "msg_2048",
      "channelSequence": 9823144,
      "senderUserId": "U123",
      "text": "Deploy is complete in production.",
      "createdAt": "2026-07-26T07:10:00Z",
      "threadReplyCount": 3
    }
  ],
  "nextCursor": "seq_9823094"
}
  • 200History returned
  • 401Authentication required
  • 403Caller cannot read this channel
  • 404Channel not found
PATCH/api/v1/workspaces/{workspaceId}/channels/{channelId}/read-cursor

Advances the caller's read cursor for a channel. This drives unread counts, bold channel state, mention badges, and notification suppression.

Request


{
  "lastReadMessageId": "msg_2048",
  "lastReadSequence": 9823144,
  "readAt": "2026-07-26T07:10:05Z"
}

Response


{
  "channelId": "C456",
  "lastReadSequence": 9823144,
  "unreadCount": 0,
  "mentionCount": 0
}
  • 200Cursor updated
  • 400Cursor does not belong to this channel
  • 401Authentication required
  • 403Caller is not a channel member
GET/api/v1/workspaces/{workspaceId}/search/messages

Searches messages in the workspace. The search service must filter results to channels and DMs the user is allowed to read.

Request


q=deploy%20complete&channel=C456&from=2026-07-01&limit=20

Response


{
  "results": [
    {
      "messageId": "msg_2048",
      "channelId": "C456",
      "snippet": "Deploy is complete in production.",
      "createdAt": "2026-07-26T07:10:00Z"
    }
  ],
  "nextCursor": "search_after_41"
}
  • 200Search results returned
  • 401Authentication required
  • 403Workspace search disabled or restricted
  • 429Search rate limit exceeded
POST/api/v1/workspaces/{workspaceId}/channels

Creates a public channel, private channel, direct message, or group direct message. For DMs, the channel name can be omitted and membership is explicit.

Request


{
  "type": "private",
  "name": "incident-db-latency",
  "memberUserIds": ["U123", "U456", "U789"],
  "retentionPolicyId": "ret_90_days"
}

Response


{
  "workspaceId": "W123",
  "channelId": "C999",
  "type": "private",
  "name": "incident-db-latency",
  "createdAt": "2026-07-26T07:11:00Z"
}
  • 201Channel created
  • 400Invalid name, type, members, or policy
  • 401Authentication required
  • 403Caller cannot create this channel type
  • 409Channel name already exists in workspace

Separate HTTP APIs from the WebSocket event stream. Message creation, history, channel management, search, and cursor updates are request-response APIs. Real-time message delivery, presence deltas, typing indicators, and reconnect catch-up are event streams. Message send should be idempotent because clients retry aggressively during mobile and network failures.

Database Design

The primary data model is tenant scoped. Every durable row starts with workspace_id so authorization, retention, backups, exports, and cost attribution can be enforced per tenant. Channels represent public channels, private channels, DMs, and group DMs; the type and membership determine visibility.

Messages are appended to a channel log with a monotonic channel_sequence. This gives stable pagination, per-channel ordering, unread cursor math, and replay after WebSocket reconnect. Threads are represented by thread_root_message_id, so a thread is a filtered view over the same message log plus optional secondary indexes.

workspaces
workspace_iduuidPrimary tenant identifier
namevarchar(200)Workspace display name
planvarchar(32)Free, business, enterprise, or grid
created_attimestampTenant creation time
default_retention_policy_iduuid nullableDefault message retention policy
regionvarchar(32)Home region for writes and compliance residency
statusvarchar(20)Active, suspended, exporting, or deleted
channels
workspace_iduuidPartition and tenant scope
channel_iduuidUnique channel, DM, or group DM id
typevarchar(20)Public, private, dm, group_dm, or announcement
namevarchar(80) nullableRequired for named channels, nullable for DMs
created_by_user_iduuidCreator or system user
created_attimestampCreation time
member_countbigintApproximate count used for fanout strategy
last_sequencebigintLatest assigned per-channel sequence
retention_policy_iduuid nullableOverrides workspace default when present
channel_memberships
workspace_iduuidTenant scope
channel_iduuidChannel membership belongs to
user_iduuidMember user id
rolevarchar(20)Member, admin, guest, or bot
joined_attimestampMembership start for history visibility
last_read_sequencebigintRead cursor for unread computation
last_read_attimestampCursor update time
notification_levelvarchar(20)All, mentions, muted, or none
is_archivedbooleanWhether the user has hidden the channel
messages
workspace_iduuidTenant scope and first partition key component
channel_iduuidChannel log partition
message_iduuidGlobally unique message id
channel_sequencebigintMonotonic order within the channel
sender_user_iduuidHuman, bot, or app sender
thread_root_message_iduuid nullableNull for root messages
texttextNormalized message text or pointer to encrypted content
created_attimestampAuthoritative server timestamp
edited_attimestamp nullableLatest edit time
deleted_attimestamp nullableSoft deletion for audit and retention
versionintOptimistic concurrency for edits
compliance_statevarchar(20)Normal, retained, legal_hold, redacted

Indexes

  • messages(workspace_id, channel_id, channel_sequence) is the main history index and supports ordered pagination.
  • messages(workspace_id, thread_root_message_id, channel_sequence) supports thread views without scanning the whole channel.
  • channel_memberships(workspace_id, user_id) lists a user's channels for sidebar rendering and reconnect subscription.
  • channel_memberships(workspace_id, channel_id, user_id) authorizes send, history, and search result visibility.
  • channels(workspace_id, name) is unique for public and private named channels.
  • The search index stores tokenized message text with workspace id, channel id, message id, created_at, and access-filter metadata. Search results must still be permission checked.

Relationships

A workspace owns channels, memberships, messages, retention policies, and audit logs. A channel has many memberships and many messages. A DM is modeled as a channel with fixed membership, which keeps message storage and unread logic uniform. Message edits and deletes are best represented as new events linked to the original message id so compliance systems can reconstruct history when policy requires it.

NoSQL alternatives

At production scale, the message log is better served by a distributed wide-column or log-structured store such as Cassandra, DynamoDB, Bigtable, or a custom append log. Partition by workspace and channel, order by channel_sequence, and split very hot channels into time buckets or virtual shards while preserving a logical sequence.

Membership and read cursors can live in a low-latency key-value store with write coalescing. Presence should be ephemeral in Redis or a purpose-built in-memory state service, not in the durable message database. Search belongs in OpenSearch, Elasticsearch, Vespa, or a similar inverted index fed from the message event stream. Compliance archives can be stored in immutable object storage with tenant-specific retention policies.

High-Level Architecture

Drag to pan · Ctrl/⌘ + scroll to zoom

The hot write path persists the message, emits an event, and lets fanout, search, notifications, presence, and compliance progress independently. WebSocket gateways own connections; channel fanout decides where each message should be pushed.

Slack has two primary planes. The durable collaboration plane handles authentication, channel membership, message ordering, message storage, history, retention, and search. The real-time plane maintains millions of WebSocket connections and pushes events to online users with low latency.

The Message Service should not directly write to every recipient. It appends a message to the channel log, records a durable event, and returns an acknowledgment. Channel Fanout consumes the event, resolves members and active sessions, and pushes only to online clients. Offline users catch up from history and read cursors rather than requiring every message to be precomputed into a per-user inbox.

Enterprise features are not side features. Workspace id is present in every data path, search index document, audit log, and compliance export. This is the key difference from a personal messenger: channels are organizational spaces with searchable history, access control, retention, and legal governance.

Request Flow

  1. 1

    Client establishes real-time session

    The client authenticates through the API Gateway and opens a WebSocket to the Realtime Gateway. The gateway stores connection id, user id, workspace ids, device metadata, and last seen event id. It also starts heartbeats for presence.

  2. 2

    User sends a channel message

    The client calls POST /messages with workspace id, channel id, clientMessageId, text, optional threadRootMessageId, and attachments. The Message Service validates size, rate limits, idempotency, and sender identity.

  3. 3

    Membership and policy are checked

    The service checks whether the user is a member of the channel, whether posting is allowed, whether retention or legal hold policies require special handling, and whether the message violates workspace policy.

  4. 4

    Channel sequence is assigned

    The service allocates the next monotonic channel_sequence for this channel. This can be done by a per-channel sequencer, a partition-local counter, or a log append offset. The sequence is the order clients use for history and replay.

  5. 5

    Message is persisted with an outbox event

    The message row and a fanout event are committed durably before the caller receives success. The outbox pattern prevents a message from being stored without a corresponding fanout, search, notification, and compliance event.

  6. 6

    Fanout resolves online recipients

    Channel Fanout consumes the message event, reads cached channel membership and notification preferences, finds active sessions for online members, and sends the event to the right Realtime Gateway shards.

  7. 7

    Clients receive and acknowledge

    The Realtime Gateway pushes the message to connected clients. Clients render it in sequence order and later update read cursors. Missed events during disconnect are recovered by replaying from the last known sequence or by fetching history.

  8. 8

    Unread, mentions, search, and compliance update asynchronously

    Workers update mention counters, push notifications, search indexes, audit logs, and compliance archives. These systems can lag temporarily, but they must be replayable from the durable message event log.

Core Components

Realtime Gateway

Terminates WebSocket connections and pushes events to online clients.

The gateway maintains connection state, handles heartbeats, tracks resume tokens, applies backpressure per connection, and forwards messages only to sessions assigned to that gateway shard. It should be stateless enough that clients can reconnect elsewhere after failure.

Message Service

Owns message validation, ordering, persistence, edits, deletes, and history reads.

This service is the consistency boundary for the channel log. It checks authorization, assigns channel_sequence, writes the message, records the outbox event, and serves paginated history. It should not depend on search or push notifications for the send path.

Channel Fanout Service

Turns committed channel events into targeted WebSocket deliveries.

Fanout workers consume message events, load membership and session mappings, split work by channel and gateway shard, and deliver to online users. They choose different strategies for small channels, large channels, and announcement channels.

Membership and Cursor Store

Answers who can see a channel and where each user has read up to.

Membership data is used by send authorization, history reads, search filtering, fanout, and unread computation. Read cursors are high-write-volume records and should be idempotent, coalesced, and cached aggressively.

Message Store

Durable ordered log for channel messages and thread replies.

The store is partitioned by workspace and channel, sorted by channel_sequence, replicated across availability zones, and backed up for retention. It supports history pagination and replay after reconnect.

Presence Service

Maintains ephemeral active and away state for users and devices.

Presence receives heartbeats from gateways, computes coarse state transitions, suppresses noisy updates, and publishes deltas to interested users. It should not be treated as durable truth and can degrade during overload.

Search and eDiscovery Pipeline

Indexes messages and supports secure search, retention, and legal workflows.

The pipeline consumes message events, tokenizes text, stores permission metadata, applies retention policies, and exports immutable records for compliance. Search results must be filtered by membership and policy at query time or through secure index partitioning.

Notification Workers

Sends mention, keyword, mobile push, and email notifications.

Notification workers evaluate mentions, mute rules, do-not-disturb windows, user presence, device tokens, and workspace policy. They should deduplicate aggressively because the same user may have multiple devices and sessions.

Deep Dive

Per-channel ordering

Slack users expect everyone in a channel to agree on message order. The simplest mental model is an append-only log per channel: each accepted message receives a monotonically increasing channel_sequence, and clients render messages by that sequence.

A single global order is unnecessary and harmful because two unrelated channels do not need to coordinate. Per-channel ordering lets the system partition by channel, scale independent hot paths, and make replay straightforward. The hard part is hot channels. A channel with very high write QPS can bottleneck on one sequencer or partition.

Common mitigations include partition-local log offsets, a lightweight per-channel sequencer, leasing sequence ranges to a leader, or splitting huge announcement channels into virtual fanout shards while preserving one logical display order. Edits and deletes should not reorder the log; they are versioned events attached to the original message.

Fanout strategy for small and huge channels

Small channels can use push fanout: when a message is committed, fanout workers resolve online members and push the event to each active connection. This gives low latency and simple client behavior.

Huge channels need a different approach. Pushing a single message to 100K members synchronously can overload membership stores, WebSocket gateways, and network links. For announcement channels, deliver immediately to online users in batches, shard by gateway, and let offline users catch up from the channel log. Avoid writing one inbox row per recipient unless a product feature truly requires it.

A strong answer separates durable history from delivery attempts. The message exists once in the channel log. Real-time delivery is a best-effort acceleration for online users. This keeps the system closer to Slack than to an email queue.

Unread counts and read cursors

Unread state is deceptively expensive. A naive design increments a counter for every member on every message. That explodes for huge channels and creates high write amplification.

A better design stores a per-user per-channel last_read_sequence and computes unread as latest_channel_sequence minus last_read_sequence, adjusted for joins, deletes, muted channels, and mention-only counters. For small channels or sidebar performance, maintain cached summary counters that can be repaired from the authoritative cursor and message log.

Mention counts are separate from general unread counts. Mentions are sparse enough to materialize as per-user mention events, but they still need deduplication, retention handling, and mute logic. Cursor writes should be idempotent and monotonic so repeated client updates are safe.

Presence at millions of connections

Presence is high-volume and low-criticality. At 15M peak connections and one heartbeat every 30 seconds, the raw heartbeat stream is about 500K events per second. Persisting every heartbeat would be wasteful.

Realtime Gateways should aggregate heartbeats locally, publish only state changes, and use TTLs for liveness. The Presence Service stores ephemeral device and user state in memory or Redis, then emits coarse deltas such as active, away, or offline. Clients do not need second-by-second accuracy.

Degrade presence first during incidents. Messaging should remain available even if presence is stale or disabled. Interviewers like to see this priority because it shows that not all real-time features have the same reliability target.

Search indexing and compliance

Search is essential in Slack because channels form an organizational memory. The message send path should publish durable events to an indexing pipeline. Indexers tokenize text, extract mentions and attachments, apply language analyzers, and store workspace id, channel id, message id, timestamp, and access metadata.

Search must enforce permissions. A user should never see private-channel or DM results after leaving the channel unless policy explicitly allows it. This can be enforced with filtered queries over membership, per-tenant index partitions, or secure document-level access filters.

Enterprise compliance adds retention, legal hold, immutable audit history, and eDiscovery exports. Deletes may hide content from normal users while preserving it for legal hold. Therefore, the system needs policy-aware storage rather than a single hard-delete path.

Multi-tenant workspace isolation

Every layer should know the workspace boundary. API auth, message partitions, search indexes, encryption keys, audit logs, quotas, and operational dashboards should be scoped by workspace or enterprise organization.

Isolation protects privacy and reliability. A large customer's export job, search spike, or huge channel should not starve smaller tenants. Apply per-tenant rate limits, queue partitions, storage quotas, and noisy-neighbor controls. For highly regulated customers, isolate indexes, keys, or even clusters by enterprise tier.

This is another major contrast with WhatsApp. Slack's core unit is the workspace with admin policy and channels; WhatsApp's core unit is personal conversations. That difference drives the whole HLD.

Scaling

Prototype: one region and durable relational storage

Start with a stateless API service, a WebSocket gateway fleet, a relational database for workspaces, channels, memberships, and messages, and Redis for presence. Use simple per-channel counters for ordering and a background worker for notifications and search.

Growth: event bus, caches, and separated workers

Introduce a pub/sub bus for message events, separate fanout workers from the Message Service, cache channel memberships and read cursors, and move search indexing and notification delivery to independent consumers. This prevents optional features from slowing message sends.

Large scale: partitioned message logs and WebSocket sharding

Move message history to a distributed store partitioned by workspace and channel. Shard WebSocket connections by user, workspace, or gateway assignment. Fanout workers publish to gateway shards instead of holding direct client connections.

Enterprise scale: huge channels and tenant isolation

Use special fanout modes for announcement channels, virtual shards for hot channels, per-tenant quotas, dedicated search indexes for large customers, and lazy unread computation for massive memberships. Add detailed audit logs, retention policies, and export pipelines.

Global scale: regional routing and data residency

Route users to nearby WebSocket gateways while keeping workspace writes in the workspace home region when data residency requires it. Replicate read-only history, search, and presence summaries where allowed. Design reconnect and catch-up so regional failover does not lose messages.

Bottlenecks & Optimizations

Huge channel fanout explosion

Do not synchronously write a per-user delivery row for every member. Batch by gateway shard, push to online users only, rely on history catch-up for offline users, and compute unread lazily from cursors. For announcement channels, consider broadcast topics and rate-shaped delivery.

WebSocket gateway connection pressure

Shard connections across many gateway hosts, use efficient heartbeat intervals, cap per-connection buffers, support resume tokens, and shed low-priority events such as typing indicators before message events. Keep reconnect storms under control with jittered backoff.

Membership lookup amplification

Fanout, search, history, and send authorization all need membership data. Cache memberships by channel and user, version membership lists, invalidate on joins and leaves, and avoid scanning full membership lists for every small decision.

Hot channel write partition

A busy incident or company-wide channel can overload one channel partition or sequencer. Use per-channel sequencer leases, time-bucketed partitions, virtual shards for fanout, and careful backpressure when one channel exceeds safe write QPS.

Read cursor write amplification

Clients can update cursors frequently while users scroll. Coalesce cursor updates per user and channel, accept only monotonic advances, batch writes, and derive sidebar counts from cached latest channel sequence plus the authoritative cursor.

Search indexing lag

Search consumers can fall behind during traffic spikes. Partition index streams by workspace, autoscale consumers, expose freshness indicators, and keep message history usable even when search is stale. Rebuild indexes from the durable message log.

Failure Handling

Realtime Gateway failure

Clients reconnect with exponential backoff and a resume token. The new gateway asks for events after the last acknowledged sequence or directs the client to fetch history. Presence may flicker, but messages remain durable in the channel log.

Message store degradation

If writes cannot be durably committed, message send should fail fast or queue only when the product accepts delayed sends. Do not acknowledge messages that may be lost. Read paths can fall back to replicas with clear freshness tradeoffs.

Pub/sub or fanout lag

Message sends still succeed once persisted, but real-time delivery becomes delayed. Monitor consumer lag, scale fanout workers, and let clients recover by polling history or replaying after reconnect. The durable log is the source of truth.

Search index unavailable

Disable or degrade search while preserving send, history, and real-time delivery. Keep indexing offsets so consumers can catch up later. Show freshness or outage indicators instead of returning incomplete results as authoritative.

Notification provider outage

Queue notifications with expiration, deduplicate on retry, and suppress stale mobile pushes after the user reads the message elsewhere. Never block message persistence or WebSocket delivery on push provider availability.

Authorization or tenant isolation bug

Fail closed for private channels and DMs, use defense-in-depth checks in API, fanout, search, and history, and keep audit logs for access decisions. Roll back quickly and invalidate potentially leaked search or membership caches.

Security

Authentication and authorization

All APIs and WebSocket sessions require authenticated users or apps. Every send, history read, search result, export, and fanout decision must verify workspace membership, channel membership, role, guest restrictions, and app permissions.

Tenant isolation

Workspace id must be part of storage keys, cache keys, search documents, metrics, audit logs, and rate limits. Large enterprises may require dedicated encryption keys, isolated indexes, or region-specific clusters.

Private channels and DMs

Private-channel and DM content should never be delivered, indexed for visibility, or exposed in notifications to unauthorized users. Membership changes must invalidate caches and search filters quickly.

Encryption and key management

Use TLS for clients and service-to-service traffic, encrypt data at rest, and support tenant-specific key management for enterprise customers. Some customers may require customer-managed keys and auditable key access.

Retention, deletion, and legal hold

Normal deletion, retention expiry, and legal hold can conflict. The system must apply policy before physical deletion, preserve immutable audit history where required, and make user-visible redaction separate from compliance retention.

Abuse and spam controls

Rate-limit message sends, mentions, channel creation, app integrations, file sharing, and search. Detect mention spam, compromised bots, suspicious invite patterns, and automated scraping of workspace history.

Auditability

Admin actions, permission changes, exports, retention policy changes, app installations, and compliance searches should be logged immutably with actor, target, timestamp, and workspace context.

Tradeoffs

Pros

  • +Per-channel logs give simple history, replay, ordering, and cursor math.
  • +Separating persistence from fanout keeps message sends durable even when real-time delivery lags.
  • +WebSocket gateways scale independently from message storage and search.
  • +Lazy unread computation avoids huge per-recipient write amplification.
  • +Tenant-scoped data models support enterprise isolation and compliance.

Cons

  • Per-channel ordering can create hot partitions for extremely active channels.
  • Search and compliance pipelines introduce eventual consistency and operational complexity.
  • Presence and WebSocket connection management require large memory and reconnect-storm handling.
  • Permission filtering across search, history, fanout, and notifications is easy to get wrong.
  • Huge channels force special fanout and unread strategies that are more complex than small chat rooms.

Alternatives

Alternative one is fanout-on-write to per-user inboxes. It makes each user's unread and sidebar views fast, but it explodes for huge channels and creates many writes per message.

Alternative two is pure fanout-on-read from channel logs. It minimizes write amplification and works well for offline users, but online real-time delivery and unread badges need additional caches and event streams.

Alternative three is an end-to-end encrypted personal messenger model. It improves message privacy from the provider, but it conflicts with Slack-like enterprise search, retention, moderation, and eDiscovery requirements.

When not to use this design

Do not use this design when the product requires default provider-blind end-to-end encryption for personal conversations. Slack-like workspace messaging intentionally supports organizational search, admin policy, compliance exports, and integrations, which require different tradeoffs from private personal messaging.

Follow-up Questions

How do you guarantee message ordering?

Guarantee order within a channel, not globally. Assign a monotonic channel_sequence when the message is durably appended, and have clients render by that sequence. Different channels can be ordered independently.

What happens when a user is offline?

The message remains in the channel log. Offline users do not need real-time delivery rows for every message. When they reconnect, the client uses read cursors, last seen sequence, and history APIs to catch up.

How should unread counts be computed?

Store a per-user per-channel last_read_sequence and compare it with the channel's latest sequence. Cache sidebar summaries for speed, but keep the cursor and message log as the repairable source of truth. Mention counts can be materialized separately because they are sparse.

How do you handle a 100K-member channel?

Use special fanout. Persist the message once, push to online members in gateway-sharded batches, avoid per-member writes for offline users, and compute unread lazily. Apply rate limits and backpressure if one channel begins to dominate capacity.

How accurate does presence need to be?

Presence is useful but not as critical as messages. Use heartbeats with TTLs, publish only state changes, tolerate staleness, and degrade presence first during overload. Do not persist every heartbeat to the message database.

How do you keep search secure?

Include workspace id, channel id, message id, and access metadata in the index, then filter results by current membership and policy. For sensitive tenants, use isolated indexes or stronger document-level access controls. Recheck permissions before opening a result.

How is this different from WhatsApp?

Slack is organized around workspaces, channels, searchable history, admin policy, integrations, and compliance. WhatsApp is centered on personal or small-group conversations and often emphasizes end-to-end encryption. The storage, search, fanout, and governance choices differ accordingly.

Company Variations

Amazon

Amazon interviewers may push on operational excellence: DynamoDB-style partition keys, noisy-tenant isolation, alarms, backpressure, and how the system keeps sending messages when search, notifications, or presence fail. Be ready to discuss cost of fanout and read cursor writes.

Microsoft

Microsoft is likely to emphasize Teams-like enterprise collaboration, identity integration, tenant administration, compliance, data residency, eDiscovery, and customer-managed keys. Explain how workspace policy flows into storage, search, export, and audit logs.

Meta

Meta may focus on massive real-time fanout, WebSocket connection management, presence, feed-like delivery tradeoffs, and ranking notification importance. Contrast channel-based workplace messaging with personal messaging and discuss privacy boundaries.

LinkedIn

LinkedIn may frame this around professional communities, enterprise messaging, search relevance, spam control, and notification quality. Emphasize member graph permissions, search filtering, and avoiding noisy mention notifications.

Interview Tips

Lead with the core domain difference: Slack is workspace and channel messaging, not just personal chat. Draw the message send path first: auth, sequence, durable append, event bus, fanout to WebSocket gateways. Then add unread cursors, presence, search, notifications, and compliance as separate systems. Keep repeating the reliability hierarchy: messages and history first, then real-time delivery, then presence and typing indicators.

What interviewers expect

  • Start with workspace, channel, membership, message, and cursor data models.
  • Separate durable message append from asynchronous fanout, search, notifications, and compliance.
  • Use WebSocket gateways for online delivery and history replay for offline catch-up.
  • Explain per-channel ordering and why global ordering is unnecessary.
  • Discuss huge-channel fanout, presence heartbeats, and read cursor write amplification.
  • Call out enterprise-grade tenant isolation, retention, audit logs, and eDiscovery.

Common mistakes

  • !Treating Slack like a simple two-person chat app and ignoring workspaces, channels, and tenant policy.
  • !Acknowledging message sends before durable persistence.
  • !Writing one unread or inbox row per member for every huge-channel message.
  • !Forgetting per-channel ordering and reconnect replay.
  • !Letting search or notifications block the send path.
  • !Ignoring private-channel permissions in search and fanout.

Red flags

  • ×No concrete capacity math for WebSocket connections, fanout, storage, or presence.
  • ×No strategy for channels with tens or hundreds of thousands of members.
  • ×No workspace isolation model for enterprise tenants.
  • ×No read cursor design for unread counts.
  • ×No failure story for fanout lag or gateway reconnects.
  • ×No compliance, retention, or eDiscovery discussion.

Revision Notes

  • Slack is built around workspaces, channels, DMs modeled as channels, threads, memberships, and per-user read cursors.
  • The send path should authenticate, authorize, assign a per-channel sequence, durably append the message, publish an event, and acknowledge.
  • Real-time delivery over WebSocket is an acceleration path for online users. Offline users catch up from history.
  • At 1B messages per day, average write QPS is about 11,600 and 10x peak is about 116,000.
  • With 20 online recipients per message, average fanout is about 231,000 deliveries per second and peak is about 2.3M deliveries per second.
  • Millions of WebSocket connections require sharded gateways, heartbeat aggregation, resume tokens, per-connection backpressure, and reconnect-storm control.
  • Use per-user per-channel last_read_sequence for unread counts. Avoid per-recipient writes for every huge-channel message.
  • Search indexing is asynchronous but must enforce workspace, channel, retention, and membership permissions.
  • Presence is ephemeral and can be stale. Degrade presence before message sending or history.
  • Enterprise Slack requires tenant isolation, audit logs, retention policies, legal hold, compliance exports, and eDiscovery.

Flashcards

Quiz

0/7 answered

  1. 1.What ordering guarantee is most appropriate for Slack messages?

  2. 2.Why should message fanout be asynchronous after persistence?

  3. 3.What is the best source of truth for general unread counts?

  4. 4.At 15M peak WebSocket connections with one heartbeat every 30 seconds, what is the approximate heartbeat rate?

  5. 5.What should happen if the search index is down?

  6. 6.Which design is most appropriate for a 100K-member announcement channel?

  7. 7.What is the main product difference between Slack and WhatsApp in HLD terms?

Cheat Sheet

Goal: design workspace-based team messaging with channels, DMs, threads, real-time WebSocket delivery, durable history, unread cursors, presence, search, mentions, notifications, and enterprise compliance.

Core model: workspace, user, channel, membership, message, channel_sequence, thread_root_message_id, read cursor, notification preference, retention policy.

Send path: client calls send API, service authenticates, checks membership, assigns per-channel sequence, appends message, writes outbox event, acknowledges, then workers fan out, index, notify, and archive.

Real-time path: clients maintain WebSocket connections to Realtime Gateways. Fanout workers push committed events to online sessions. Offline clients replay from history using sequence and read cursor.

Scale math: 1B messages per day is about 11,600 write QPS average and 116,000 peak. With 20 online recipients, fanout is about 231,000 deliveries per second average and 2.3M peak. 15M connections with 30-second heartbeats is 500,000 heartbeats per second.

Storage: 2 KB per message times 1B per day is about 2 TB raw per day. One year with replicas and indexes reaches petabyte scale.

Ordering: guarantee per-channel order through channel_sequence. Do not require global ordering across workspaces or channels.

Unread: store last_read_sequence per user and channel. Compute unread from latest channel sequence, with cached sidebar summaries and separate mention events.

Huge channels: persist once, fan out to online users by gateway shard, avoid per-member synchronous writes, and rely on history for offline users.

Search and compliance: index asynchronously from durable events, enforce membership and retention, preserve audit and legal hold records, and support tenant-scoped eDiscovery exports.

Reliability hierarchy: durable messages and history first, real-time delivery second, search and notifications third, presence and typing indicators last.

References