At a Glance
- Async processing decouples producers and consumers — neither needs to be available at the same time.
- Message queues absorb traffic spikes; task queues retry failed work automatically.
- At-least-once delivery + idempotent consumers is the most common and practical approach.
- Kafka retains messages after consumption — consumers can replay events from any point.
- Back pressure is the system's way of saying "slow down" — handle it or cascade-fail.
- Event sourcing + CQRS are powerful patterns built on top of async messaging.
Why Go Asynchronous?
When a user signs up, you might want to: create the account, send a welcome email, resize their avatar, update analytics, and notify the CRM. Doing all five synchronously means the user stares at a spinner while five systems (each with their own failure modes) hold their request hostage.
The async alternative: do the essential bit (create the account), drop messages onto a queue for everything else, and respond immediately. Three superpowers follow:
- Decoupling: the signup service doesn't know or care that an email service exists. Either can deploy, crash, or scale independently.
- Spike absorption: a traffic burst piles up in the queue instead of overwhelming workers — consumers drain it at their own sustainable pace.
- Reliability: failed work stays on the queue and gets retried. A flaky email provider becomes a delay, not an outage.
The queue is a buffer: producers burst, consumers drain steadily, nobody waits on anybody.
Why Async? The Core Insight
Strip away the tooling and the idea is simple. A synchronous call means the caller waits for the callee to finish — if the callee is slow, the caller is slow; if the callee is down, the caller fails. An asynchronous call means the caller puts work in a queue and moves on; the callee processes it at its own pace, whenever it can.
That one change buys you three distinct kinds of decoupling:
Temporal decoupling
Producer and consumer don't need to be up at the same moment. The email service can be mid-deploy while signups keep flowing — the messages simply wait.
Rate decoupling
The producer can spike to 10,000 msgs/sec for a minute while the consumer steadily drains 1,000/sec. The queue absorbs the difference, and the consumer catches up later.
Failure decoupling
If a consumer crashes mid-task, the unacked message stays on the queue and is retried when it restarts. The producer never even knows there was a problem.
Same work, different experience: async returns immediately and lets the slow part happen off the critical path.
Delivery Guarantees: Pick Your Poison
Networks drop packets and consumers crash mid-task — so "the message arrives exactly once" is harder than it sounds. Every messaging system offers one of three contracts:
Fire and forget — no retries. Messages can vanish, but never duplicate. Fine for metrics, logs, presence pings where the next data point supersedes the lost one anyway.
Redeliver until the consumer acknowledges. Nothing is lost — but if a consumer finishes the work and crashes before acking, the message is delivered again. Duplicates are a fact of life. The industry default.
The unicorn. True exactly-once delivery is impossible over an unreliable network; what systems offer is exactly-once processing — at-least-once delivery plus deduplication or transactions. Kafka and SQS FIFO get close, with real overhead.
Design consumers so processing the same message twice is harmless. Use an idempotency key: "have I already processed payment msg_8f3a? Then ack and skip." At-least-once delivery + idempotent consumers gives you exactly-once effects — the pattern behind Stripe's API and most payment systems.
Message Delivery Guarantees — Deep Dive
Knowing the three contracts is table stakes; knowing how to implement them is what interviews probe. Here's the practical playbook for each:
- At-most-once: fire and forget — no retries, no acks. Fast and simple. OK for: metrics, logs, cache-invalidation hints — anything where losing a message is cheaper than handling a duplicate.
- At-least-once: the producer retries until an ack is received, so the consumer may see duplicates. The fix is on the consumer side: make processing idempotent — the same message processed twice produces the same result. This is the most practical guarantee, and the industry default.
- Exactly-once: the hardest. Requires producer deduplication + consumer idempotency + transactional commit, all together. Kafka's Transactions API does this — at real overhead. Reach for it only when truly needed (financial ledgers, stream aggregations that must not double-count).
Before doing the work, ask "did I already process payment_id=abc123?" A payment service checks its processed-IDs table (or SETNX in Redis) before charging — the second delivery of the same message becomes a no-op instead of a double charge.
The Outbox Pattern: Killing the Dual-Write Problem
A classic bug: your service writes an order to the database, then publishes an OrderCreated event to the queue. If it crashes between the two, the DB and the queue disagree forever. The transactional outbox pattern eliminates this:
In one DB transaction, write the order row and a row in an outbox table describing the event. Either both commit or neither does.
A separate poller (or CDC tool like Debezium) reads unpublished outbox rows and publishes them to the queue.
After the broker acks, mark the outbox row as published. A crash between publish and mark just means a duplicate publish — which your idempotent consumers already handle.
The guarantee: the message is published if and only if the transaction committed. No more dual-write inconsistency.
Event Streaming: The Kafka Model
A queue deletes a message once consumed. Kafka flips the model: it's a durable, append-only log that consumers read at their own pace.
- Topics — named streams of events ("orders", "clicks").
- Partitions — each topic splits into N ordered logs, spread across brokers. Partitions are the unit of parallelism; events with the same key (e.g., one user) land in the same partition, preserving their order.
- Offsets — each consumer tracks its own position (offset) per partition. Consuming doesn't delete anything.
- Consumer groups — instances in a group split the partitions among themselves; separate groups each get the full stream. Analytics, search-indexing, and fraud teams can all read the same events independently.
- Retention & replay — events persist for days or forever. Deployed a buggy consumer? Fix it, rewind the offset, and reprocess history. Queues can't do that.
This makes Kafka the backbone for event sourcing (the log is the source of truth), audit trails, change-data-capture, and stream processing (Flink, Kafka Streams). LinkedIn built it to move trillions of events per day; Uber, Netflix and Airbnb run their nervous systems on it.
Kafka Architecture Deep Dive
The previous section gave you the model; here's the machinery underneath, piece by piece:
- Topic: a named stream of records. Think of it as a database table — but append-only and ordered.
- Partition: the unit of parallelism. A topic is split into N partitions spread across brokers; each partition is an ordered, immutable log.
- Producer: publishes to a topic and chooses the partition — round-robin for spread, or by key. The same key always lands in the same partition, so order is guaranteed per key (e.g., all events for one user stay in order).
- Consumer group: N consumers share a topic, each assigned some partitions. Parallelism = min(consumers, partitions) — add consumers beyond the partition count and the extras sit idle.
- Offset: a consumer group's position in each partition's log, tracked independently per group. Rewind to any offset to replay history.
- Retention: messages are kept for a configurable duration (default 7 days) or size limit — regardless of consumption. Consumers can lag and catch up.
- Replication: each partition has one leader + N-1 followers (the ISR — In-Sync Replicas). All reads and writes go to the leader; a follower is promoted if the leader dies.
One topic, three partitions, two consumer groups — each group reads the full stream independently at its own offset.
Task Queues: Async for Application Code
A task queue wraps a message broker in developer-friendly clothing: you enqueue function calls, and worker processes execute them with retries, scheduling, and result tracking built in.
# Celery: this runs in a worker, not in the web request
@app.task(max_retries=3, retry_backoff=True)
def send_welcome_email(user_id):
user = db.get_user(user_id)
mailer.send("welcome", to=user.email)
# In the request handler — returns instantly:
send_welcome_email.delay(user.id)
Ecosystem picks: Celery (Python), Sidekiq (Ruby), BullMQ (Node). Classic jobs: sending email, resizing images and video transcoding, generating PDFs and reports, syncing with third-party APIs — anything slower than ~100 ms that the user doesn't need to wait for.
Back Pressure: When the Queue Fights Back
Queues hide overload — until they don't. If producers enqueue at 1,000/s and consumers drain at 600/s, the queue grows by 400/s forever. Latency balloons (messages wait hours), memory or disk fills, and the broker — your decoupling hero — becomes the outage.
Back pressure means propagating "slow down!" signals upstream instead of buffering infinitely:
- Bound the queue — a full queue rejects new work fast, so producers get an error now rather than silence followed by collapse.
- Rate-limit producers — throttle at the source (HTTP 429, client backoff).
- Auto-scale consumers — alarm on queue depth / consumer lag and add workers before the backlog hurts.
- Shed load deliberately — drop low-value messages or short-circuit with a circuit breaker (Topic 10) and serve a degraded experience instead of dying.
A queue-based system can be "100% up" while every message waits 45 minutes. Queue depth and consumer lag are the heartbeat metrics of async systems — alert on them like you'd alert on 500s.
Choosing a Tool
| RabbitMQ | Kafka | AWS SQS | GCP Pub/Sub | |
|---|---|---|---|---|
| Model | Smart broker, routing exchanges | Distributed append-only log | Managed simple queue | Managed pub/sub stream |
| Replay old messages | No (consumed = gone) | Yes — rewind offsets | No | Limited (seek/retain) |
| Ordering | Per queue | Per partition | FIFO queues only | Per ordering key |
| Throughput | High | Extreme (millions/s) | High (auto-scales) | High (auto-scales) |
| Ops burden | Self-managed (or cloud) | Heaviest (or use managed) | Zero | Zero |
| Sweet spot | Task routing, RPC-ish work queues | Event streaming, event sourcing, analytics pipelines | Decoupling on AWS with no ops | Streaming on GCP with no ops |
Rule of thumb for interviews: need work done (emails, thumbnails)? Say task queue / SQS / RabbitMQ. Need facts recorded and shared (events many systems consume, possibly re-read later)? Say Kafka. Saying "Kafka" for a simple email job is like buying a freight train to deliver a pizza.
Event Sourcing and CQRS
Traditional systems store current state: a users row holds the latest email, the latest address. The question "what was this account's state last Tuesday?" requires a separate audit log — if anyone remembered to build one.
Event Sourcing flips it: store the events (facts), not the state. UserCreated, EmailChanged, OrderPlaced — an append-only sequence of things that happened. Current state is derived by replaying all events.
Benefits
A full audit trail for free, time travel ("state at any T"), and replayability — rebuild any projection by re-running the event log.
Costs
Eventual consistency between events and derived views, more complex queries, and snapshots needed for performance (replaying 10M events on every read is not a plan).
Who uses it
Banks (every transaction is an event — your balance is literally a replay), the Axon framework, and Kafka-centric architectures where the log is the source of truth.
CQRS: Separate Writes from Reads
CQRS (Command Query Responsibility Segregation) splits the system into a write model (commands that change state) and a read model (queries that read state):
- Write model: normalized, optimized for consistency and correctness of updates.
- Read model: denormalized, optimized for queries — materialized views, Elasticsearch indexes, Redis caches.
- Sync: events from the write model update the read model asynchronously — usually via the very queues and streams this topic covers.
Discord runs a CQRS-like pattern: message writes go to Cassandra (the write model), while Elasticsearch powers search (a read model fed asynchronously from the write path).
Saga Pattern — Distributed Transactions
Microservices each own their database — so no single ACID transaction can span "create order, charge card, reserve stock." A saga replaces the impossible distributed transaction with a sequence of local transactions, each publishing events that trigger the next step — and each paired with a compensating transaction to undo it if a later step fails.
Choreography saga
No central coordinator — each service listens for events and reacts. OrderService creates the order, PaymentService charges the card, InventoryService reserves stock, ShippingService schedules delivery. On failure, PaymentService publishes OrderPaymentFailed and OrderService cancels the order. Decoupled — but the flow lives nowhere, so it's hard to understand and debug.
Orchestration saga
A central Saga Orchestrator tells each service what to do and triggers compensations on failure. Much easier to understand and debug — one place holds the whole flow — at the cost of a component that's a single point of failure if it goes down. Used by Uber (Cadence/Temporal) and Netflix (Conductor).
Dead Letter Queues and Poison Pills
A poison pill is a message that always crashes the consumer — malformed JSON, a null field that triggers an exception. Without protection, the loop is grim: consumer crashes, restarts, re-receives the same message, crashes again. Forever.
The fix is a Dead Letter Queue (DLQ): after N failed processing attempts (typically 3), the message is moved out of the main queue and into the DLQ, where it can't hurt anyone.
- What a DLQ enables: manual inspection, debugging the bad payload, replaying after a fix ships, and alerting ("DLQ depth > 0" is a great alarm).
- AWS SQS: built in — configure
maxReceiveCounton the source queue and attach a DLQ. - Kafka: no built-in DLQ — implement it in the consumer: try to process, and on repeated failure produce the message to a
.DLT(dead letter topic) and commit the offset.
Tool Comparison: RabbitMQ vs Kafka vs SQS vs Pub/Sub
The earlier table gave the highlights; here's the fuller spec sheet you'd want before committing:
| Feature | RabbitMQ | Apache Kafka | AWS SQS | Google Pub/Sub |
|---|---|---|---|---|
| Model | Message broker (push) | Event log (pull) | Queue (pull) | Message broker (push/pull) |
| Message retention | Until consumed | Configurable (default 7 days) | 4 days (max 14) | 7 days (default) |
| Throughput | Moderate (~50K msgs/s) | Very high (millions/s) | High | High |
| Replay | No | Yes (seek to offset) | No | Yes (seek) |
| Ordering | Per-queue FIFO | Per-partition | FIFO queues only | Per-key ordering |
| Delivery | At-least-once | At-least-once (exactly-once with transactions) | At-least-once | At-least-once |
| Best for | Task queues, routing, RPC | Event streaming, log aggregation, analytics | Simple decoupling on AWS | GCP services, pub/sub patterns |
| Examples | Celery, background jobs | Kafka Streams, Flink | Microservices on AWS | Firebase, GCP pipelines |
Interview Q&A
Three messaging questions interviewers actually ask — click to reveal strong answers.
Message queue (RabbitMQ, SQS): task-based — one consumer processes each message, and the message is deleted after consumption. Use for: background jobs, email sending, image processing, webhook delivery. Event streaming (Kafka): an event log that multiple consumer groups read independently, with messages retained. Use for: audit logs, analytics pipelines, event sourcing, and feeding multiple downstream systems (payments → analytics + fraud detection + notifications simultaneously). The key question: do you need replay, multiple independent consumers, or long retention? Kafka. Just want reliable task execution? A queue.
Design consumers to be idempotent. Three techniques: (1) Unique constraint: try the INSERT, catch the duplicate-key error, treat it as success. (2) Check-then-process: SELECT first, only process if not seen before — but beware race conditions; use SELECT FOR UPDATE or a Redis SETNX lock. (3) Idempotency key: the client provides a unique ID with each request; the server stores processed IDs in Redis or the DB and deduplicates. The pattern: if redis.setnx(f"processed:{msg_id}", 1, ex=3600): process(msg) — only the first delivery is processed.
(1) Priority queues: separate queues for CRITICAL (account security), HIGH (order updates), LOW (marketing). CRITICAL uses SQS FIFO or Kafka priority partitions and is processed first. (2) Fanout: one event ("order_placed") → fanout service → separate queues per channel (email, push, SMS), each with dedicated consumers calling SES, FCM, and Twilio. (3) Rate limiting: email providers have per-second limits — use a token bucket limiter per provider. (4) Retry: SQS visibility timeout plus a dead letter queue for failures. (5) Scale check: 100M/day ≈ 1,160/sec average, ~5,000/sec peak — Kafka handles this trivially with 10 partitions × 10 consumers per channel.
Anti-Patterns to Avoid
Using async for synchronous user flows
Don't put checkout behind a message queue if the user is waiting for order confirmation. Async makes sense for fire-and-forget (send the receipt email), not for blocking operations (charge the card).
Unbounded queue growth
If consumers are consistently slower than producers, the queue fills → RAM/disk exhaustion → system crash. Always monitor queue depth and lag, and auto-scale consumers.
Missing idempotency
"We're at-least-once, no big deal" leads to double-charging users, duplicate emails, and ghost inventory updates. Every consumer MUST be idempotent under at-least-once delivery.
Event-Driven Architecture — Core Concepts
Event-Driven Architecture (EDA) is a system design pattern where components communicate through events rather than direct synchronous calls. When something happens in one service, it publishes an event; other services listen and react asynchronously — without the producer knowing who consumes it.
Key Characteristics: Asynchronous processing (non-blocking workflows); Loose coupling (services only share event contracts, not direct dependencies); Scalability & flexibility (new consumers can be added without modifying producers).
Synchronous vs. Asynchronous: In synchronous (request-response) systems, the caller blocks until a response arrives — tight coupling, simple to reason about. In EDA, the producer fires an event and continues — the consumer processes it independently, enabling higher throughput and resilience.
Pub-Sub vs. Event Streaming: Pub-Sub (RabbitMQ, AWS SNS) broadcasts events to subscribers — events are transient, consumed once, and have no strict ordering. Event Streaming (Kafka, AWS Kinesis) persists events in an ordered log that consumers can replay — ideal for audit trails, event sourcing, and reprocessing historical data.
Key components: Event Producers generate events; Event Brokers (Kafka, RabbitMQ, AWS EventBridge) transmit and optionally store them; Event Consumers process events asynchronously; Event Store provides log-based persistence for replaying events.
Best Practices: Use idempotent event processing to handle duplicate deliveries safely; implement Dead-Letter Queues (DLQs) for failed messages; version events using a schema registry to handle schema evolution without breaking consumers.
Event-Driven Architecture Interview Questions
Key interview questions covering EDA fundamentals, scalability, implementation technologies, and real-world use cases.
Fundamentals
Event-Driven Architecture (EDA) is a software architecture pattern where system components communicate through events rather than direct synchronous calls. Instead of services invoking each other directly, they publish events when something happens, and other services listen for and react to those events asynchronously.
Differences from Request-Response: Decoupling — in REST-based systems, services depend on each other directly; in EDA services are decoupled and interact only through events. Scalability — EDA scales better as new consumers can subscribe to events without modifying producers. Asynchronous Processing — traditional architectures require waiting for responses; EDA enables non-blocking workflows.
Pub-Sub (RabbitMQ, AWS SNS, Redis Pub/Sub): One-to-many event distribution. Events are transient — once consumed, they are gone. No strict ordering guarantee. Best for real-time notifications where consumers need only the latest event.
Event Streaming (Apache Kafka, AWS Kinesis): Events are stored and can be replayed. Maintains strict event order within partitions. Events persist for later processing. Best for processing historical data, event sourcing, and audit logs where consumers may need to replay past events.
1. Event Producers: Emit events when something happens (e.g., a user places an order, a sensor reports a reading).
2. Event Brokers: Middleware that routes events — examples: Kafka, RabbitMQ, AWS EventBridge. Acts as the communication backbone between producers and consumers.
3. Event Consumers: Services that process events asynchronously and react (update a database, send a notification, trigger a workflow).
4. Event Store (optional): A persistent log of all events used for auditing, debugging, or event replay to reconstruct past system state.
Scalability & Fault Tolerance
Challenges: Eventual Consistency — events propagate asynchronously so data may not be updated instantly across all services; Event Ordering — ensuring correct sequence in distributed systems; Debugging Complexity — events flow across many services making it hard to trace issues; Handling Failures — consumers may fail while processing events.
Handling Eventual Consistency: Use idempotent operations to avoid duplicate updates; implement Sagas (orchestration/choreography patterns) to ensure business logic correctness; leverage Event Sourcing to reconstruct system state from the ordered event log if inconsistency is detected.
Partitioning: Kafka uses partitions to ensure ordering within each partition — events for the same key (e.g., same user ID) always go to the same partition.
Event Versioning: Maintain a version number in events and process them sequentially, rejecting out-of-order versions.
Global Ordering Service: Use a dedicated ordering service that assigns sequence numbers to events before they reach consumers.
Deduplication: Implement unique event IDs so consumers can detect and discard already-processed or out-of-order duplicate events.
A Dead-Letter Queue (DLQ) is a separate queue where failed or unprocessable messages are sent after exhausting retry attempts.
Importance: Prevents infinite retries — avoids a faulty message being retried indefinitely and blocking the queue; Facilitates debugging — developers can inspect dead-lettered events to identify root causes; Ensures reliability — prevents faulty messages from blocking the main queue and causing backpressure across the system.
In Kafka, a dedicated topic stores dead-lettered messages for later analysis and reprocessing after the root cause is fixed.
Implementation & Technologies
Apache Kafka: Event Streaming type. Stores events for replay with guaranteed ordering within partitions. Highly scalable with partitions. Best for large-scale event processing and analytics pipelines.
RabbitMQ: Pub-Sub type. Transient messages (once consumed, gone). No strict ordering guarantee. Scales horizontally. Best for real-time messaging and task queues where replay is not needed.
AWS EventBridge: Managed Event Bus. No persistence. No strict ordering. Scales with AWS infrastructure. Best for cloud-native event integration between AWS services and external SaaS applications.
Idempotency ensures that processing the same event multiple times does not produce unintended side effects — critical in at-least-once delivery systems.
Use Unique Event IDs: Track processed event IDs in a database or cache to prevent re-processing the same event twice. Store Processed States: Maintain a "processed" flag in the database for each event. Ensure Business Logic is Idempotent: Avoid unconditional increments or timestamps — check current state first. Leverage Broker Deduplication: Kafka allows deduplication via log compaction; SQS has a deduplication ID feature.
Use Cases & Real-World Applications
E-commerce Order Processing System: When a customer places an order, multiple actions must happen: the Order Service records the order, the Payment Service processes payment, the Inventory Service updates stock, and the Notification Service sends a confirmation.
Using EDA, each service listens for order events asynchronously, with no direct dependencies on each other. Benefits: scalability (each service scales independently), fault isolation (a payment failure does not crash inventory), and flexibility (new services like fraud detection or analytics can be added by simply subscribing to order events without modifying existing code).
When events change over time (adding/removing fields), breaking changes can occur. Best practices:
Backward Compatibility: Ensure new consumers can still process older event formats. Versioning: Include a schema_version field in events and maintain handlers for different versions. Schema Registry: Use tools like Kafka Schema Registry (with Avro or Protobuf) to validate schema changes before they reach consumers. Field Deprecation: Instead of removing fields immediately, mark them as deprecated and stop producing them gradually over multiple releases. Event Transformation Layer: Introduce an intermediary service that translates older event schemas to the new format for legacy consumers.
Messaging & Queues Interview Questions
Key interview questions on asynchronous messaging, queue design, and decoupling strategies.
Asynchronous messaging decouples the sender and receiver, allowing them to operate independently. This improves scalability because producers don't wait for consumers; resilience because messages are queued even if the consumer is slow or down; performance because users get a fast response while background tasks (e.g., sending emails, processing orders) are offloaded; and loose coupling because systems can evolve independently without tight runtime dependencies. A classic example: sending a confirmation email after a purchase — there is no need to delay the user response for email delivery.
RabbitMQ uses a queue-based push model with FIFO ordering per queue. Messages are deleted once consumed, making it ideal for real-time, transactional workloads that need per-message acknowledgment and complex routing (e.g., order processing, real-time updates). Kafka uses a log-based pull model with per-partition ordering and configurable retention — messages are kept even after consumption, enabling replay. Kafka is best for high-throughput data pipelines, event sourcing, and analytics (e.g., tracking user behavior at scale). RabbitMQ delivers at-most-once or at-least-once guarantees; Kafka can achieve exactly-once with the right configuration.
At-most-once: The message is sent once and not retried on failure. It is fast and simple but can lose data, making it appropriate for non-critical logging or telemetry. At-least-once: The message is retried until acknowledged, which is the default in most systems. It may result in duplicates so consumers must be idempotent. Suitable for order placement and email dispatching. Exactly-once: The message is delivered exactly once with no duplication or loss. It is complex and expensive to implement but required for critical financial transactions such as bank transfers and billing systems.
Queues act as a buffer between producers and consumers, so producers can send messages quickly without waiting for slow consumers. Consumers can be scaled horizontally and independently of producers to keep up with load. For fault tolerance: if a consumer crashes, messages remain safely in the queue until it recovers or another consumer instance takes over. Queues also smooth traffic spikes — during high-traffic events like Black Friday, the queue absorbs the surge and lets backend systems catch up at their own pace without being overwhelmed.
Common problems include queue backlog when consumers can't keep up, increased message delay leading to latency spikes, message duplication from retries, and resource exhaustion from CPU and memory strain caused by deep backlogs. Mitigations: scale consumers horizontally using auto-scaling groups; optimize message processing to reduce per-message work; use dead-letter queues (DLQ) to isolate and inspect poison messages that repeatedly fail; and implement rate limiting and backpressure mechanisms to signal producers to slow down when the system is under strain.
When a user places an order, the frontend service acts as a producer and publishes an order message to a queue. An Order Service (consumer) reads from the queue, validates stock, reserves inventory, and triggers downstream services for payment and confirmation emails — each of which may publish their own events in an event-driven chain. The queue provides value by decoupling payment and inventory logic from the frontend (users get an instant response), handling retries automatically on transient failures, and allowing each downstream service to scale independently based on its own load.
Assign each message a unique message ID (e.g., Order ID or broker-generated Message ID) and maintain a processed-message store (in Redis or a database table) that tracks messages already handled. Before processing a message, check whether its ID already exists in the store — if so, skip it. Make business operations inherently idempotent: for example, "mark order as paid" should be a no-op if the order is already marked paid. Leverage built-in deduplication features where available (e.g., AWS SQS FIFO queues have a deduplication window). This is essential in at-least-once delivery scenarios where retries are expected.
Further Reading
Quick Check
Your payment consumer might receive the same "charge customer" message twice (at-least-once delivery). What's the right defense?
Interactive: Message Queue Visualiser
Produce messages and watch the queue fill up. Drag the consumer speed slider to see backpressure build when production outpaces consumption. Flood the queue to trigger the high-load warning.