SQS vs SNS vs EventBridge: Queue, Topic or Event Bus?
8 min readBytePatterns
When to use SQS, SNS or EventBridge: pull from a queue, push a copy to every subscriber, or route by content. Plus fan-out, dead-letter queues and idempotency.
AWS has three services that all "send messages", and they are not three versions of the same thing. SQS is a queue that consumers pull work from. SNS is a topic that pushes a copy of each message to every subscriber. EventBridge is an event bus that looks inside each event and routes it by its content. Once you see which question each one answers, they combine more often than they compete.
Every behaviour below is taken from the AWS documentation listed at the end. Numbers are documented defaults and limits as of September 2026; check them before you build.
The problem it solves
Services that call each other directly are coupled in time: if shipping is deploying when the order service calls it, the order fails. Messaging breaks that coupling. The producer hands a message to AWS and moves on; the consumer processes it when it can. The three services differ in who receives the message and how.
The intuition
SQS: one queue, competing consumers, pull. Producers send messages to a queue. Consumers poll it. A received message is not removed; it stays in the queue but becomes invisible to other consumers for the visibility timeout (30 seconds by default). The consumer deletes it when done. If it is not deleted in time — the consumer crashed, or took too long — it becomes visible again and another consumer can receive it. Each message is meant for one consumer, and work simply waits through bursts and outages.
Standard queues deliver at least once. More than one copy of a message might be delivered, and messages may occasionally arrive out of order, although the queue makes a best-effort attempt to preserve order. FIFO queues process messages in strict order within a message group, and they do not introduce duplicates when a send is retried within the five-minute deduplication interval.
SNS: one topic, every subscriber gets a copy, push. A publisher sends one message to a topic, and SNS pushes it to every subscription: SQS queues, Lambda functions, HTTP(S) endpoints, email, SMS, mobile push and more. When a subscriber must be able to fall behind or go offline and catch up later, its subscription is usually an SQS queue: the copy waits there until the service is back.
EventBridge: one bus, routing by content. Events arrive on an event bus — from AWS services, your own applications or SaaS partners. Rules hold event patterns that match on the event's data, and a matching event is sent to the rule's targets. A single rule can send to several targets. The producer does not know who is listening; the rules decide.
So the choice is really three questions. Does one worker need to do this job eventually? Queue. Do several independent consumers each need their own copy? Topic. Should the event's content decide where it goes? Bus.
Watch it run
The animation follows one order through the classic fan-out: the order service publishes to an SNS topic, which pushes a copy into a billing queue and a shipping queue. Shipping is down, so its copy waits. Billing receives the order twice — at-least-once delivery — and skips the duplicate. A malformed message fails on every receive until SQS moves it to the dead-letter queue. Finally the topic is swapped for an EventBridge bus whose rule routes only the matching events.
SQS vs SNS vs EventBridge
Step 1 of 10
An order is placed. Billing and shipping both care — and the order service should not need to know either of them exists.
The same interactive animation as the lesson — step through it with the controls.
The code
Most of the behaviour that matters is in the consumer, not the infrastructure. The Python below is a toy model of two documented SQS rules, not the AWS API: a message that is not deleted becomes visible again, and a queue with a redrive policy moves a message to the dead-letter queue once it has been received maxReceiveCount times. The consumer is idempotent, keyed by order ID:
from collections import Counter, deque
def drain(messages, handle, max_receive_count):
"""A toy model of two documented SQS rules, not the AWS API:
a message that is not deleted becomes visible again, and one that
has been received max_receive_count times is moved to the DLQ."""
queue, dlq, receives = deque(messages), [], Counter()
while queue:
msg = queue.popleft()
if receives[msg["id"]] == max_receive_count:
dlq.append(msg["id"]) # set aside; the line moves on
continue
receives[msg["id"]] += 1
try:
handle(msg) # success: the consumer deletes it
except ValueError:
queue.append(msg) # not deleted: it comes back later
return dlq, dict(receives)
shipped = []
def ship(msg): # idempotent: keyed by order ID
if msg["order"] in shipped:
return # a duplicate delivery: skip it
if msg["address"] is None:
raise ValueError("malformed address") # fails on every receive
shipped.append(msg["order"])
deliveries = [
{"id": "m1", "order": 9, "address": "12 Quay St"},
{"id": "m1", "order": 9, "address": "12 Quay St"}, # at-least-once: again
{"id": "m2", "order": 10, "address": None}, # the poison message
{"id": "m3", "order": 11, "address": "4 Mill Rd"},
]
print(drain(deliveries, ship, max_receive_count=3))
# (['m2'], {'m1': 2, 'm2': 3, 'm3': 1})
print(shipped) # [9, 11]
Order 9 was delivered twice and shipped once, because the consumer checked its key. The poison message was received three times and then set aside, so order 11 behind it still shipped.
The infrastructure side of the same fan-out, from the lesson, is two CLI calls. They are illustrative — the ARNs are placeholders, redrive.json holds the redrive policy — and the queue's access policy must allow the topic to send to it:
aws sns subscribe --topic-arn arn:aws:sns:us-east-1:123456789012:orders \
--protocol sqs --notification-endpoint arn:aws:sqs:us-east-1:123456789012:billing
aws sqs set-queue-attributes \
--queue-url https://sqs.us-east-1.amazonaws.com/123456789012/billing \
--attributes file://redrive.json
The complexity
The cost here is operational rather than asymptotic:
- SQS adds latency, because work waits until a consumer polls, and it adds a duty: every consumer must delete what it finishes and tolerate duplicates.
- Fan-out gives each subscriber its own queue, which means one more queue to size, monitor and give a dead-letter queue — per consumer.
- EventBridge moves routing out of code and into rules, which is flexible and also one more place to get wrong: an event that matches no rule reaches no target, and nothing in the producer notices.
Where it goes wrong
- Several services reading one queue. Consumers of one queue compete: each message goes to one of them. If each service needs every message, give each its own queue behind a topic.
- A visibility timeout shorter than the work. The message reappears while it is still being processed and another consumer starts on it. Set the timeout above your processing time, or extend it while working. The maximum is 12 hours from the first receive.
- Non-idempotent consumers. At-least-once delivery means duplicates will arrive eventually. Record the key of each processed message and skip repeats.
- No dead-letter queue, or nobody watching it. A poison message keeps coming back without one. The DLQ must be in the same account and Region as the source queue — alarm on its depth.
- A DLQ on a FIFO queue where order is everything. Moving one message aside changes the order of the rest; the documentation advises against a DLQ when the exact order must not break.
How to say it in an interview
"SQS is a pull-based queue: one consumer processes each message, the visibility timeout hides it while in flight, and standard queues are at-least-once, so consumers are idempotent. SNS is pub/sub: every subscriber gets a copy, so for fan-out I put one SQS queue per service behind a topic, and each gets its own pace and its own dead-letter queue. EventBridge routes by event content through rules, which fits when many producers and consumers shouldn't know about each other."
For the consumer side, see the Lambda and event-driven lesson, where a function polls an SQS queue in batches.
Sources
- Amazon SQS standard queues — Amazon SQS Developer Guide
- Exactly-once processing in Amazon SQS — Amazon SQS Developer Guide
- Amazon SQS visibility timeout — Amazon SQS Developer Guide
- Using dead-letter queues in Amazon SQS — Amazon SQS Developer Guide
- What is Amazon SNS? — Amazon SNS Developer Guide
- Subscribing an Amazon SQS queue to an Amazon SNS topic — Amazon SNS Developer Guide
- What is Amazon EventBridge? — Amazon EventBridge User Guide
- Rules in Amazon EventBridge — Amazon EventBridge User Guide