Part 6 · Async & Event-Driven Systems

Queues vs Pub/Sub

“One of you handles this” or “all of you get a copy”? Mix them up and orders go unpacked, or customers get five emails. Queues vs pub/sub, how real systems combine both, push vs pull, per-key ordering, and delete vs keep.

Lesson 358 min readintermediate#messaging#pub-sub

On this page

  1. The Problem
  2. The Core Idea
  3. How It Works
  4. Trade-offs
  5. In the Real World
  6. Interview Questions
  7. Key Takeaways
  8. Further Reading

The Problem

When an order is placed, your system publishes a message. A message is a small piece of data that one service sends to another. Three teams care about this message:

  • the email team wants to send a confirmation,
  • the warehouse team wants to start packing,
  • the analytics team wants to record the sale.

You put the message on a queue (a waiting line for messages), and all three services read from it. Surprise: each message goes to only one of them. The email goes out, but the warehouse never hears about the order. Or the warehouse packs it, but no email is sent.

Now the opposite mistake. You run five copies of the email service to handle the load. All five are subscribed to a broadcast topic (a channel that sends each message to every subscriber). The result is that every customer gets five confirmation emails.

The difference between "one of you handles this" and "all of you get a copy" is the difference between a queue and publish/subscribe (pub/sub for short). Mixing them up causes lost work or repeated work.

The Core Idea

Think about two ways of handing out work in an office.

A queue is a single task tray. Tasks go into one tray, and whichever worker is free takes the next task. Each task is done once, by one worker. More workers means tasks get done faster.

Pub/sub is a company announcement. The manager announces "Q3 results are out!" on the office speaker. Everyone who is subscribed hears it (sales, finance, HR), and each reacts in their own way. The manager does not know or care who is listening.

Queue — point to point
  • •one message → ONE consumer
  • •like a single task tray
Use whenWork that must be done once: resize, email, charge
Pub/sub — broadcast
  • •one message → EVERY subscriber
  • •like an office announcement
Use whenEvents many services react to: order placed, user signed up
The one distinction that matters. · compare

How It Works

Point-to-point queues

Diagram
                          ┌──► Worker 1  (takes msg A)
Producer ──► [ Queue ] ───┼──► Worker 2  (takes msg B)
                          └──► Worker 3  (takes msg C)
  • Each message is processed by exactly one of the consumers. This is the competing consumers pattern (lesson 18). A consumer is a program that reads messages. Here the consumers compete for the next message.
  • Adding workers increases throughput (the amount of work done per second).
  • When a consumer acknowledges a message (tells the queue "I finished it"), the message is removed from the queue.

Use it when a piece of work should be done once: resize this image, send this email, charge this invoice, generate this report.

Publish/subscribe

Diagram
                               ┌──► Email service
Publisher ──► [ Topic: orders ]┼──► Warehouse service
                               └──► Analytics service
  • The publisher (the sender) sends a message to a topic (a named channel).
  • Every subscriber (a receiver that signed up to the topic) gets its own copy.
  • Subscribers are independent. A slow analytics service does not slow down emails.
  • The publisher is decoupled, which means it does not depend on its subscribers. New subscribers can be added without changing it.

Use it when one event matters to several different services: "order placed", "user signed up", "payment failed".

The real-world answer: combine both

Usually you want both behaviours at once:

  • every type of service gets a copy (pub/sub between services), and
  • within each service, only one instance handles each copy (a queue within a service).

Pattern 1: Topic fan-out to queues (AWS SNS + SQS style)

Fan-out means one message is copied to many places. SNS is Amazon's pub/sub topic service. SQS is Amazon's queue service.

  1. 1
    Order service
    publishes “order placed” once
  2. 2
    SNS topic: orders
    copies to every subscribed queue
  3. 3
    Email workers × 5
    one email per order, however many workers
  4. 4
    Warehouse workers × 3
    one packing job per order
  5. 5
    Analytics workers × 2
    one record per order
Topic fan-out to queues (SNS → SQS). Every service gets a copy; inside each service, workers compete. · flow

Each service gets its own queue subscribed to the topic. Within a queue, workers compete. Result: one email, one packing job and one analytics record per order, no matter how many worker instances there are.

Pattern 2: Kafka consumer groups

Kafka (lesson 36) has this built in. Kafka is a system that stores messages in a log (an ordered list that you only add to). Consumers join a consumer group, which is a named team of consumers that share the work:

  • Different groups each get every message (pub/sub).
  • Within a group, each message goes to one member (queue).
P0
01234567
P1
01234567
P2
01234567
P3
01234567
P4
01234567
P5
01234567
each box = one message at an offset; order is kept within a partition
Group A
consumer-1P0, P3
consumer-2P1, P4
consumer-3P2, P5
Group B: reads all 6 partitions again, at its own pace

Inside one group each partition has exactly one consumer, so the partition count caps your parallelism. Every extra group gets its own full copy of the stream: that is pub/sub.

Kafka does both at once. Consumer groups each get the whole stream; inside a group, each partition has one owner. · interactive

Pattern 3: RabbitMQ exchanges

RabbitMQ is a popular message broker. A broker is a server that receives, stores and delivers messages. In RabbitMQ, producers (senders) send messages to an exchange, which routes them to queues by rules:

  • Direct exchange: route by an exact routing key, a label on the message (order.created → the order queue).
  • Fanout exchange: copy to all bound queues (pure pub/sub).
  • Topic exchange: route by patterns, where * stands for exactly one word (order.*.india → the India team's queue).
  • Headers exchange: route by message headers.

Push vs pull

  • Push: the broker sends messages to consumers as they arrive (RabbitMQ by default, SNS to HTTP endpoints, Google Pub/Sub push). It has low latency, but consumers need flow control (for example, a "prefetch" limit, which caps how many unfinished messages a consumer may hold) so they are not overwhelmed.
  • Pull: consumers ask for messages when ready (SQS, Kafka, Google Pub/Sub pull). Consumers control their own pace. This gives natural back pressure, which means a busy consumer simply asks for fewer messages, so the system slows down safely.

Ordering

"Messages arrive in the order they were sent" is harder than it sounds when you have many consumers:

  • With competing consumers, message 1 might go to a slow worker and message 2 to a fast one, so message 2 finishes first.
  • Global ordering (everything in one strict order) limits you to one consumer at a time, which greatly lowers throughput.
  • The practical solution is per-key ordering: all messages for the same key (like the same order ID or user ID) stay in order, while different keys are processed in parallel.
    • Kafka does this with partitions (a topic is split into partitions, and the same key always goes to the same partition).
    • SQS FIFO queues (first in, first out) do it with message group IDs.
Text
Order 5521: created → paid → shipped    (must stay in order)Order 7730: created → paid              (independent, can run in parallel)

Retention: delete or keep?

  • Classic queues (RabbitMQ, SQS) delete messages once acknowledged. The queue is a to-do list.
  • Log-based systems (Kafka, Pulsar, Kinesis, Redis Streams) keep messages for a set time (days, or even forever in Kafka and Pulsar), even after they are read. Consumers track their position in the log, called an offset. The system is a history book, and a new consumer can replay the past.

Replay means reading old messages again. It is very useful. You can add a new service and let it process last week's events. Or you can fix a bug and run the old events again.

SystemModelNotes
RabbitMQQueues + exchanges (routing)Flexible routing, mature, push-based (it sends messages to consumers)
Amazon SQSQueueFully managed (run by AWS), simple; FIFO option
Amazon SNSPub/sub topicsFan-out to SQS, Lambda, HTTP, email/SMS
Google Cloud Pub/SubTopics + subscriptionsManaged; each subscription behaves like a queue
Apache KafkaPartitioned log + consumer groupsHigh throughput, retention, replay
Apache PulsarLog + queue semanticsMulti-tenant, tiered storage
NATSLightweight pub/sub (+ JetStream for persistence)Very fast, simple
Redis Pub/Sub / StreamsFire-and-forget pub/sub (no delivery guarantee) / log-like streamsPub/Sub has no storage: offline subscribers miss messages. Streams persist

Trade-offs

  • Queues: simple work distribution, and each task is handled once. But there is one "audience" per queue, and consumed messages are gone.
  • Pub/sub: loose coupling (services do not depend on each other), and it is easy to add new reactions to events. But it is harder to see "who does what", and every subscriber must handle duplicates and failures itself.
  • Fan-out (topic → queues): the best of both, but it needs more parts (a topic plus a queue per service).
  • Log-based (Kafka): replay, history and very high throughput, but there are more concepts (partitions, offsets, consumer groups) and more work to run it.
  • Strict ordering: easier to reason about, but less parallelism (less work done at the same time). Prefer per-key ordering.

When not to use pub/sub: when exactly one service should act, or when the "event" is really a command to a specific service ("charge this card"). An event says "something happened". A command says "do this". Send commands to that service's queue or API directly.

In the Real World

AWS SNS + SQS fan-out. This is one of the most common AWS patterns. An "order created" SNS topic fans out to separate SQS queues for email, fulfilment (packing and shipping), fraud checks and analytics. Each team scales its own workers and handles its own failures without affecting the others.

Kafka consumer groups at LinkedIn, Uber and Netflix. Companies using Kafka publish events like "profile viewed", "trip completed" or "play started" once, and many independent consumer groups (search indexing, analytics, recommendations, billing, fraud detection) each read the full stream at their own pace.

Google Cloud Pub/Sub. Each subscription on a topic gets every message, and multiple workers pulling from the same subscription share the load. This is the queue-inside-pub/sub pattern, offered as a managed service.

Redis Pub/Sub for live features. Chat and notification systems often use Redis Pub/Sub to pass messages between WebSocket gateways (lesson 33), because it is fast. They also store messages safely elsewhere (durably), because Redis Pub/Sub does not keep messages for subscribers that are offline.

Interview Questions

Q1What is the difference between a queue and pub/sub?

A queue gives each message to exactly one of the competing consumers, so it shares out work. Pub/sub gives a copy of each message to every subscriber, so it broadcasts an event to independent services.

Q2Five instances of the email service all send the same email. What went wrong?

Each instance subscribed to the broadcast topic on its own, so each got its own copy. They should share one subscription instead. That means one SQS queue behind the topic, one Kafka consumer group, or one Pub/Sub subscription. Then each message goes to only one instance.

Q3How do you guarantee ordering and still scale consumers?

Use per-key ordering. Send all messages with the same key (order ID, user ID) to the same partition or message group. That group is processed in order, while different keys run in parallel. Global ordering allows only one consumer at a time.

Q4Push or pull consumers?

Push gives low latency, but it needs flow control (prefetch limits) so consumers are not overwhelmed. Pull lets each consumer take work at its own pace. This gives natural back pressure and makes batching easy.

Q5When should you not use pub/sub?

When exactly one service must act. That is a command (“charge this card”), not an event. Send commands to the queue or API of the service that owns the job, so it is clear who is responsible.

Key Takeaways

  • A queue delivers each message to one consumer (work distribution). Pub/sub delivers each message to every subscriber (event broadcast).
  • Real systems usually combine them: pub/sub between services, with a queue within each service. Examples are SNS→SQS fan-out, Kafka consumer groups and RabbitMQ exchanges.
  • Pull gives consumers control of their own pace. Push gives low latency but needs flow control.
  • Prefer per-key ordering (partitions, message groups) over global ordering.
  • Classic queues delete messages once handled. Logs keep them, which allows replay and new consumers.

Further Reading

  • Azure Architecture Center patterns "Publisher-Subscriber" and "Competing Consumers"
  • The RabbitMQ documentation "AMQP 0-9-1 Model Explained" (exchanges, queues and bindings)
  • The AWS documentation on fanning out SNS messages to SQS queues
  • The Google Cloud Pub/Sub overview documentation
  • System Design Interview Volume 2 by Alex Xu and Sahn Lam (chapter "Distributed Message Queue")

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related series