AlgoInsight

System Design

How WhatsApp Delivers a Message in Milliseconds - to 2 Billion People

You hit send, and a fraction of a second later two grey ticks appear. Between those two moments, a message travels through nine distinct stages of a planetary-scale system. Here's the whole journey - and the three patterns that make it work.

By Admin2026-07-075 min readwhatsappreal-timemessagingsystem-designkafkadistributed-systems

You type a message, hit send, and almost instantly two ticks appear confirming it was delivered. It feels like nothing - a message going from your phone to your friend's. But behind that fraction of a second sits one of the most impressive real-time systems ever built: a platform delivering messages to over two billion people, reliably, in milliseconds, whether the recipient is holding their phone or it's asleep in their pocket. What actually happens in that instant is a journey through nine distinct stages of a distributed system, and understanding it is a masterclass in how real-time messaging works at planetary scale.

Let's trace the whole journey of a single message - from the moment you hit send to the two ticks that come back - and pull out the three patterns that make it all work. (This walks the delivery mechanism in depth; for how you'd approach designing a system like this in an interview, see the companion "Design WhatsApp" case study.)

The architecture at a glance

Before the journey, meet the components. A message passes through a series of specialized services, each doing one job:

graph TD
    P[Your device<br/>persistent socket] --> LB[Load Balancer]
    LB --> CG[Connection Gateway]
    CG --> CS[Chat Service<br/>stateless]
    CS --> PR[Presence Service<br/>Redis]
    CS --> Q[(Message Queue<br/>Kafka)]
    Q --> MS[(Message Store<br/>Cassandra)]
    Q --> CG2[Recipient's<br/>Connection Gateway]
    CS --> NS[Notification Service<br/>APNs / FCM]
    CG2 --> R[Recipient device]
    NS --> R
This diagram shows WhatsApp's message-delivery architecture. Your device holds a persistent socket to a Load Balancer, which routes to a Connection Gateway holding your live session. The Gateway passes the message to a stateless Chat Service, which checks the Presence Service backed by Redis to see if the recipient is online, and publishes the message to a durable Kafka message queue. From the queue, one consumer writes the message to the Cassandra message store for history, and if the recipient is online, the queue fans the message to the recipient's Connection Gateway, which pushes it to their device. If the recipient is offline, the Notification Service sends a push via APNs or FCM to wake their device.

The nine-step journey

Now the journey itself, step by step.

1 · Send

Your device holds a persistent, always-on socket - a long-lived connection (a WebSocket-style connection) that stays open rather than being created fresh for each message. This is the first key decision: keeping a connection open means a message can be sent (and received) instantly, with no connection-setup delay each time.

Before anything leaves your phone, the message is end-to-end encrypted on-device. This matters enormously: the encryption happens on your device, so the plaintext never touches WhatsApp's servers - the servers only ever handle ciphertext they can't read. The encrypted message travels over your persistent socket to the nearest edge Load Balancer, chosen to be geographically close to you for low latency.

2 · Route

The Load Balancer forwards your message to a Connection Gateway - specifically, the one that already owns your live session. This is a subtle, important detail: your persistent connection lives on a particular Gateway server, and the Load Balancer routes to that server, which already has your session context. Keeping the request going to the Gateway that owns your connection keeps the round-trip fast - no re-establishing session state elsewhere.

3 · Handle

The Gateway passes the message payload to a Chat Service - and crucially, this service is stateless. It holds no per-connection state; it just receives the payload, validates it (is it well-formed, are you allowed to send it), and decides where it needs to go. Statelessness is deliberate: because the Chat Service holds no state, you can run many identical instances behind a load balancer, and any instance can handle any message. This is how the logic layer scales horizontally to billions of users. (See the stateful vs stateless guide.)

4 · Presence check

The Chat Service asks the Presence Service - backed by Redis - a simple but decisive question: is the recipient online right now? Redis is used here because presence must be checked on essentially every message, so it needs to be blazingly fast - and in-memory Redis answers in sub-milliseconds. (See the Redis guides.) The answer to this one question determines the entire delivery path: online recipients get a real-time push; offline recipients get a wake-up notification. Presence-aware routing is the branch point of the whole system.

5 · Enqueue

The message is published to a durable message queue (Kafka). This is the architectural heart of the system, and it's worth pausing on. Publishing to a durable queue decouples "received" from "delivered." The moment the message is safely in the queue, it cannot be lost - it's durably persisted - even if delivery has to wait, retry, or ride out a traffic spike. The queue absorbs bursts, survives failures, and guarantees the message will be delivered eventually. Without this durable buffer, a delivery hiccup would mean a lost message; with it, a hiccup is just a delay. (See the message queues and Kafka guides.)

6 · Persist

A consumer reads from the queue and writes the message to the Message Store (Cassandra) - for chat history and multi-device sync. Cassandra is chosen because it's built for enormous write volume across many machines (billions of messages a day), exactly the wide-column, high-write workload it excels at. Persisting the message means it's available for history, and for syncing across a user's multiple devices (phone, desktop, web) - each device can retrieve the messages it hasn't seen.

7 · Fan-out

If the presence check said the recipient is online, the queue fans the message out to the Connection Gateway holding the recipient's live socket. Just as your message entered through the Gateway owning your connection, it now needs to reach the Gateway owning the recipient's connection - which could be a different server entirely. The fan-out routes the message to wherever the recipient's live connection lives.

8 · Deliver

That Gateway pushes the message straight down the recipient's socket - delivered in real time, because their persistent connection is already open and waiting. There's no polling, no delay; the message arrives the instant it reaches their Gateway. The recipient's device then sends an acknowledgment back through the system to you - and that's what turns your single tick into the two ticks (✓✓) confirming delivery. The acknowledgment travels the reverse path to close the loop.

9 · Offline · Push

But what if the presence check said the recipient is offline - phone asleep, app not connected? Then there's no live socket to push to. Instead, the Notification Service fires a push notification via APNs (Apple) or FCM (Google) - the platform push services that can wake a sleeping device. The notification nudges the recipient's device, which then reconnects and pulls the waiting message (which is safely stored, thanks to step 6). This is why you still get messages that arrived while your phone was off - they were persisted and waiting, and the push woke your device to fetch them.

sequenceDiagram
    participant You
    participant GW as Connection Gateway
    participant CS as Chat Service
    participant PR as Presence (Redis)
    participant Q as Queue (Kafka)
    participant DB as Store (Cassandra)
    participant R as Recipient
    You->>GW: Encrypted message (persistent socket)
    GW->>CS: Payload
    CS->>PR: Recipient online?
    CS->>Q: Publish (durable)
    Q->>DB: Persist message
    alt Recipient online
        Q->>R: Fan-out → push down socket
        R-->>You: Acknowledgment (✓✓)
    else Recipient offline
        CS->>R: Push via APNs/FCM → device pulls
    end
This sequence diagram traces a message through the system. You send an encrypted message over your persistent socket to your Connection Gateway, which passes the payload to the stateless Chat Service. The Chat Service checks the Presence Service in Redis to see if the recipient is online, and publishes the message to the durable Kafka queue, which persists it to Cassandra. If the recipient is online, the queue fans the message out and pushes it down their socket, and an acknowledgment returns to you as two ticks. If the recipient is offline, the Notification Service sends a push via APNs or FCM to wake their device, which then pulls the waiting message.

The three patterns behind it all

Strip away the specifics and the whole system rests on three patterns working together - and these three generalize far beyond WhatsApp to real-time systems everywhere:

1. Persistent connections. Every device holds a long-lived, always-open socket. This is what makes real-time possible in both directions - messages can be pushed instantly to a device without it polling, and sent instantly without connection setup. The Connection Gateways exist to hold these millions of live sockets. (Contrast this with request-response HTTP, where the server can't push to the client - real-time messaging needs the persistent connection.)

2. A durable queue. The Kafka queue decouples receiving a message from delivering it. This is what delivers reliability at scale: once a message is in the durable queue, it will not be lost, whatever happens downstream - spikes, failures, retries, offline recipients. The queue turns "deliver this now or lose it" into "this is safely stored and will be delivered." Reliability at planetary scale flows from this one decision.

3. Presence-aware routing. The Presence Service (Redis) answers "is the recipient online?" and that answer routes the message down one of two paths - real-time push to a live socket, or a wake-up notification to an offline device. This is what makes delivery both fast (online recipients get instant push) and complete (offline recipients still get the message, via push-then-pull). One fast presence check picks the right delivery strategy every time.

Persistent connections give you real-time. The durable queue gives you reliability. Presence-aware routing gives you the right delivery path. Together, they're how you get real-time delivery and reliability at the scale of two billion people.

Tip: Emeka's note from the data trenches: The single most important design decision in this whole system, in my view, is step 5 - the durable queue. It's the least flashy part and the most load-bearing. Here's why it matters so much: without it, "message received" and "message delivered" are the same event, which means anything that goes wrong between them - a downstream server hiccup, a traffic spike, a recipient's Gateway momentarily unavailable - risks losing the message. With a durable queue in the middle, "received" and "delivered" become separate, and the gap between them is absorbed safely: the message sits durably in the queue until delivery succeeds, retrying as needed, surviving spikes because the queue buffers them. I've built pipelines on exactly this principle - decouple the fast-arriving event from the slower-or-flakier processing with a durable log in between - and it's the difference between a system that loses data under stress and one that just gets briefly delayed. When you see a real-time system that's reliable at scale, look for the durable queue in the middle. It's almost always there, quietly doing the most important job.

Interview: "Design a real-time chat/messaging system" is one of the most common system design prompts, and this nine-step journey is essentially the answer. The signals interviewers look for: persistent connections (WebSockets, and Connection Gateways to hold them - recognizing that request-response HTTP can't push to clients); a durable message queue to decouple receipt from delivery (so nothing is lost under load); presence tracking (fast, in Redis) to route online-vs-offline; a message store (like Cassandra, for the high write volume) for history and multi-device sync; and push notifications (APNs/FCM) for offline delivery. Explaining why each exists - especially the durable queue for reliability and the presence check for routing - is what separates a strong answer. Naming the three patterns (persistent connections, durable queue, presence-aware routing) shows you see the architecture, not just the boxes.

Why nine steps still take milliseconds

A fair question: if a message passes through nine stages and this many services, how does it still arrive in milliseconds? The answer is that each stage is engineered to be extremely fast, and they're arranged so the critical path stays short.

The persistent socket removes connection-setup delay (the connection is already open). The Load Balancer routing to the Gateway that already owns your session avoids re-establishing state. The Chat Service is stateless and does minimal work. The presence check hits Redis, which answers in sub-milliseconds because it's in-memory. And the fan-out to the recipient's Gateway pushes down an already-open socket - no waiting for the recipient to poll. The one durable write to the queue is fast because, as covered in the durability material, appending to a log is a quick sequential operation. Some work even happens off the critical delivery path - persisting to Cassandra for history can happen in parallel with the real-time push, so it doesn't delay delivery.

So the nine steps aren't nine slow hops in sequence; they're nine fast, specialized stages, several overlapping, each optimized for its job. This is a general lesson in system design: a well-architected system can pass through many components and still be fast, as long as each is quick and the critical path is kept lean. More services doesn't mean slower if each is fast and the slow work is moved off the path that the user is waiting on.

The bottom line

Delivering a message in milliseconds to two billion people is a nine-step journey. Your device sends an end-to-end-encrypted message over a persistent socket to an edge Load Balancer, which routes to the Connection Gateway holding your session. A stateless Chat Service validates it and checks the Presence Service (Redis) for whether the recipient is online. The message is published to a durable queue (Kafka) - decoupling received from delivered so nothing is lost - and persisted to the Message Store (Cassandra) for history and sync. If the recipient is online, the queue fans out to their Gateway, which pushes the message down their live socket and returns the two-tick acknowledgment. If they're offline, the Notification Service wakes their device via APNs/FCM, and the device pulls the waiting message.

Underneath it all: persistent connections for real-time, a durable queue for reliability, and presence-aware routing for the right delivery path. Those three patterns are how you build real-time delivery and reliability at planetary scale - and they generalize to real-time systems far beyond chat. For how to approach designing a system like this in an interview, see the "Design WhatsApp" case study. We cover real-time messaging and distributed systems across System Design Fundamentals.


Go deeper

More articles