SQS vs SNS: Message Brokers vs Pub/Sub, and Why You Usually Need Both

Both make your system asynchronous, but they solve different problems. Queue semantics vs topic semantics, when to reach for each, and the SNS → SQS fan-out pattern that shows up in real system designs.

Order system fan-out — order service publishes order-placed to an SNS topic, which fans out to payments, inventory, and notification SQS queues, each with its own DLQ and consumer service.

“Should I use SQS or SNS here?” is one of those questions that sounds like a product comparison but is actually a design question. Both services make your system asynchronous. Both decouple producers from consumers. Both show up in the same architecture diagrams. But they implement two fundamentally different messaging models — and picking the wrong one gives you either lost events or accidental duplicate processing.

This post breaks down the two models (message broker/queue vs pub/sub), when to reach for each, and finishes with a system design where you use both together — which is what production systems actually do.

Async has two shapes

When someone says “make it async,” they usually mean one of two things without realizing they’re different:

  1. Work distribution — “someone, process this job, exactly once.” The message is a task. You care that it gets done, once, by any available worker.
  2. Event broadcast — “this thing happened; everyone who cares should react.” The message is a fact. You care that every interested party finds out.

SQS is built for the first. SNS is built for the second. Everything else — delivery model, retention, retries, fan-out — follows from that distinction.

What a message broker (queue) actually gives you

A queue is a durable buffer with exactly-one-consumer semantics. Producers put messages in; a pool of workers pulls them out. Each message is processed by exactly one worker.

Message broker queue: producers push into an SQS queue, competing consumers pull, failed messages redrive to a dead-letter queue

The properties that matter:

The mental model: a queue is a to-do list shared by a team. Each item gets crossed off once.

# Producer
sqs.send_message(
    QueueUrl=QUEUE_URL,
    MessageBody=json.dumps({"job": "resize_image", "key": "uploads/hero.jpg"}),
)

# Worker loop
while True:
    resp = sqs.receive_message(
        QueueUrl=QUEUE_URL,
        MaxNumberOfMessages=10,
        WaitTimeSeconds=20,  # long polling — fewer empty receives, lower cost
    )
    for msg in resp.get("Messages", []):
        process(json.loads(msg["Body"]))
        sqs.delete_message(QueueUrl=QUEUE_URL, ReceiptHandle=msg["ReceiptHandle"])

Note the shape of the contract: the worker explicitly deletes the message after processing. Until that delete, the queue considers the job unfinished.

What pub/sub (a topic) actually gives you

A topic is a broadcast channel with copy-per-subscriber semantics. A publisher sends one message; SNS delivers an independent copy to every subscription — SQS queues, Lambda functions, HTTPS endpoints, email, SMS, mobile push.

Pub/sub fan-out: one publisher sends to an SNS topic, which pushes copies to an SQS queue, a Lambda, an HTTPS endpoint, and email/SMS subscribers

The properties that matter:

The mental model: a topic is an announcement over a PA system. Everyone present hears it; anyone absent missed it.

When to reach for which

Reach for SQS when the message is a job:

Reach for SNS when the message is an event:

The cheat sheet:

SQS (queue)SNS (topic)
Consumers per messageexactly oneevery subscriber
Deliverypull (poll)push
Retentionup to 14 daysnone
Retry storyvisibility timeout + redrive to DLQper-protocol delivery retry policy
Replayyes, until deleted/expiredno
OrderingFIFO queues availableFIFO topics (must pair with FIFO SQS)
Think of it aswork distributionevent broadcast

The system design: use both

Here’s the part interviews and real systems care about. The question “SQS or SNS?” usually has a third answer: SNS → SQS fan-out.

The problem with subscribing services directly to a topic (via HTTPS or Lambda): if a subscriber is down or throttled, you’re leaning entirely on SNS’s delivery retry policy, and you have no backlog you control, no replay, no back-pressure. The problem with a bare queue: only one consumer gets each message, so you can’t broadcast.

The fix is to compose them. Publish to a topic; subscribe a queue per consumer to that topic. The topic gives you fan-out; each queue gives its owner durability, retries, replay, and independent scaling.

Order system: order service publishes order-placed to an SNS topic, which fans out to payments, inventory, and notification SQS queues, each with its own DLQ and consumer service

Walk through the failure modes, because that’s where this design earns its keep:

Two implementation details that bite people:

  1. Raw message delivery. By default, SNS wraps your payload in its own JSON envelope. Enable RawMessageDelivery on the SQS subscription unless you want every consumer unwrapping Message out of the envelope.
  2. Queue policy. The SQS queue needs a resource policy allowing sns.amazonaws.com to SendMessage, scoped to the topic ARN via a Condition on aws:SourceArn. Forgetting this is the #1 “why is my fan-out silently doing nothing” bug — the subscription exists, delivery just fails.
{
  "Effect": "Allow",
  "Principal": { "Service": "sns.amazonaws.com" },
  "Action": "sqs:SendMessage",
  "Resource": "arn:aws:sqs:ap-south-1:123456789012:payments-q",
  "Condition": {
    "ArnEquals": { "aws:SourceArn": "arn:aws:sns:ap-south-1:123456789012:order-placed" }
  }
}

TL;DR

Pick by message semantics — is this a task or a fact? — and the service choice falls out on its own.


This is part of my AWS DVA-C02 build log — hands-on labs turned into articles. If you’re studying the same exam, the fan-out pattern above (topic → queues → DLQs, filter policies, raw message delivery) maps directly to several exam scenarios.