← All essays

Async Isn't About Speed — Queues, Backpressure, Retries, Idempotency and Dead Letter Queues Explained

A queue doesn't make work faster — it makes work survivable. Sync vs async by user expectation, decoupling and burst absorption, queue depth as a latency metric, consumer scaling and backpressure, retries with exponential backoff and jitter, at-least-once delivery and idempotency, poison messages and the DLQ, lost ordering, and the eventual consistency you're signing up for.

System Design Series — Part 5 of 5


🎯 Practice This Before Your Next System Design Interview

Reading about queues, backpressure, idempotency and retry storms is one thing. Explaining them out loud, under pressure, in a 45-minute interview is a completely different skill.

intervues.club — the AI voice interview platform for real technical interviews. candidates.intervues.club — practise system design and backend interviews right now, speak your answer, get a report.

Practice the interview before the interview.


1. Where We've Been

Four posts in, here's the arc — the same systems thinking we publish on engineering and test in voice interviews:

Parts 3 and 4 made the same request cheaper. This one asks a different question:

Does this work need to happen while the user is waiting at all?

And just like caching, async is routinely explained badly in interviews:

"We'd add a queue to make it faster."

Wrong framing. A queue does not make work faster. In wall-clock terms it makes the total work slower — you've added a network hop, a serialization step, a persistence write and a poll interval.

Async isn't about speed. It's about decoupling, absorbing bursts, and isolating failure.

The response gets faster. The system gets more resilient. Those are different claims, and only the second one is the real reason.


2. Synchronous vs Asynchronous Processing

Synchronous processing means the caller waits for every step to complete before receiving a response.

The user waited 2.7 seconds. Worse: if the email provider is down, the order fails — even though the payment succeeded and the order is already in the database.

Asynchronous processing means the caller gets a response as soon as the essential work is done. Everything else is handed off.

The critical question is not "what can we move off the request path?" It's:

The test that works in interviews: would the user notice, right now, if this hadn't happened yet? If no, it can be async.


3. Producer, Queue, Consumer, Worker

The vocabulary is small and the interviewer will expect you to use it precisely.

  • Producer — anything that writes a message onto the queue. Usually your API server. It doesn't know or care who processes it.
  • Queue — a durable, ordered buffer that holds messages until they're consumed. SQS, RabbitMQ, Kafka, Redis Streams, or a Postgres table with SELECT ... FOR UPDATE SKIP LOCKED.
  • Consumer — anything that reads messages off the queue.
  • Worker — the process that actually executes the job. In practice "consumer" and "worker" get used interchangeably; strictly, the consumer polls and the worker does the work.
  • Background job — a unit of deferred work. "Send order confirmation email for order 4821."

The property that makes the whole thing valuable is right there in the diagram: producers and consumers never talk to each other.


4. Decoupling

Decoupling is the primary architectural benefit of a queue, and it operates on three axes at once.

That third one is the big one, and it's the difference between a fragile system and a resilient one.

Without a queue — coupled failure

The email provider's outage became your outage. A non-essential dependency took down an essential flow.

With a queue — isolated failure

The email provider is down for two hours. Nobody notices. When it recovers, the workers drain the backlog and every email goes out.

This is failure isolation, and it's the answer interviewers are hoping for when they ask "what if the third-party API is down?"


5. Buffering and Burst Absorption

The second reason for queues is that traffic is never smooth.

You have two choices when a burst exceeds capacity: drop the work, or buffer it.

Burst absorption works because a queue converts a capacity problem into a latency problem.

Losing 8,000 orders is a catastrophe. Processing them five seconds late is a non-event. That trade is the entire value proposition.

The essential caveat: buffering only helps for temporary bursts.

A queue in front of an under-provisioned consumer doesn't solve anything. It hides the problem until the broker runs out of memory or disk — at which point you fail anyway, and you've lost the signal about when it started.


6. Queue Depth and the Producer/Consumer Rate

Queue depth (backlog, or consumer lag) is the number of unprocessed messages sitting in the queue. It is the single most important metric in any async system — the async equivalent of the cache hit ratio from Part 4.

The arithmetic is simple and you should be able to do it out loud:

Queue depth change per second = Producer rate − Consumer rate

Producers: 1,000 msg/s
Consumers: 20 workers × 40 msg/s = 800 msg/s
Net:       +200 msg/s

After 1 hour:  720,000 backlogged messages
Drain time once producers stop: 720,000 ÷ 800 = 15 minutes

And the crucial second-order effect:

Same throughput. Same success rate. Nothing is erroring. The system is "working." But order confirmation emails are arriving fifteen minutes late, and to the user that is indistinguishable from broken.

Queue depth is a latency metric disguised as a capacity metric. Alert on it.

Alert on the trend and on the age of the oldest message, not just the absolute count. A depth of 50,000 that drains in 30 seconds is fine. A depth of 500 that hasn't moved in ten minutes means your consumers are dead.


7. Consumer Scaling

This is where async delivers its scaling payoff, and it's a direct callback to Part 2.

Consumers are almost always stateless — pull a message, do work, acknowledge. That makes consumer scaling the easiest horizontal scaling in your entire architecture.

Autoscaling on queue depth is one of the cleanest control loops in distributed systems:

But — and this is what separates a good answer from a great one — consumer scaling has a ceiling, and the ceiling is everything the consumer depends on.

That's bottleneck migration from Part 2, showing up again. Scaling workers from 10 to 200 doesn't create capacity — it moves the constraint downstream, and now you have 200 processes hammering a database that was already at its limit.

Worse, you've built an accidental denial-of-service tool aimed at your own infrastructure, one that activates precisely when the system is already under stress.

The correct answer includes a concurrency limit on workers, sized to what the downstream dependency can actually absorb.


8. Backpressure

Backpressure is the mechanism by which a slow downstream component tells upstream components to slow down.

Without it, systems fail in the worst possible way: they accept work they cannot complete.

That failure is worse than rejecting work from the start, because you lose the accumulated backlog too.

Backpressure applies the brake at the right end:

Where backpressure shows up in real systems:

LayerBackpressure mechanism
TCPReceive window shrinks; sender slows
Connection pool (Part 3)Requests block waiting for a connection
Bounded queueenqueue blocks or fails when full
HTTP API429 with a Retry-After header
Elixir GenStage / Reactive StreamsConsumer explicitly demands N items
KafkaConsumer lag is visible; producers throttle on it

The principle worth stating out loud:

A bounded queue that rejects work is a healthy system. An unbounded queue is a system that has chosen to fail later, all at once, instead of now, a little.

Load Shedding

Load shedding is backpressure's blunt instrument: when you can't serve everything, deliberately drop the least valuable work to protect the most valuable.

The alternative to load shedding is not "everything works." The alternative is everything fails equally, including checkout.

Choosing what to drop is a design decision. Refusing to choose means the system chooses randomly, under maximum stress.


9. Retries, Exponential Backoff and Jitter

Async work fails. Networks blip, providers rate-limit, deploys restart processes. Retries are how async systems reach a reliability that plain request/response never can.

But naive retries are one of the most reliable ways to turn a small problem into an outage.

Naive retry — the amplifier

That's a retry storm — the system attacking itself. The original fault might have been a ten-second blip. The retries turned it into a forty-minute outage.

Exponential Backoff

Exponential backoff doubles the wait between attempts, so retry pressure decays instead of compounding.

delay = base × 2^attempt      (capped at a maximum)

This gives the downstream service breathing room to actually recover, which immediate retries never do.

Jitter

Backoff alone is not enough, and this is the detail most candidates miss.

If 10,000 messages fail at the same instant — because the provider went down at 14:03:22 — then with pure exponential backoff, all 10,000 retry at exactly 14:03:23. Then all 10,000 retry at 14:03:25. You've built a synchronized hammer.

Jitter adds randomness to the delay:

Full jitter:  delay = random(0, base × 2^attempt)

This is exactly the same idea as TTL jitter in Part 4. The enemy in both cases is synchronization, and the fix in both cases is randomness.

The retry decision

Not everything should be retried.

Retrying a permanent failure fifty times is pure waste. Distinguishing transient from permanent errors is part of the design, not an implementation detail.

Pair retries with a circuit breaker: after N consecutive failures against a dependency, stop calling it entirely for a cooldown period.


10. Worker Failures, Duplicate Messages and At-Least-Once Delivery

Workers die. The process gets OOM-killed, the deploy rolls, the spot instance is reclaimed, the network partitions mid-job.

The queue has to decide what happens to a message that was delivered but never acknowledged.

At-Least-Once Delivery

Nearly every production queue offers at-least-once delivery: a message will be processed one or more times, never zero.

The honest framing for an interview:

"Exactly-once" in most systems means at-least-once delivery plus idempotent consumers. There is no free exactly-once across a network boundary.

So duplicate messages are not an edge case you might encounter. They're a guaranteed, routine occurrence, and your consumer must be built for them.


11. Idempotency

Idempotency means processing the same message twice produces the same result as processing it once.

This is the most important property of an async consumer, and the one interviewers probe hardest.

Three ways to achieve it

The dedupe-store pattern in practice:

The subtlety worth mentioning: mark after success, not before, or a crash between marking and doing means the work never happens at all. And if the side effect and the dedupe record can't be written atomically, you need the side effect itself to be safely repeatable — which brings you back to strategy 1 or 3.

For external APIs, pass an idempotency key. Stripe accepts one on charge creation specifically so a retried request doesn't double-charge.

A consumer that isn't idempotent isn't finished. At-least-once delivery makes duplicates a certainty, not a risk.


12. Poison Messages and the Dead Letter Queue

A poison message is one that fails every single time it's processed. Malformed JSON. A reference to a deleted record. A field the schema didn't anticipate after a deploy.

Without protection, it's an infinite loop:

One bad message can halt an entire pipeline.

Dead Letter Queue

The Dead Letter Queue (DLQ) is a separate queue where messages go after exhausting their retries.

What the DLQ buys you:

  • The main queue keeps flowing — one broken message doesn't stall the pipeline
  • Failed messages are preserved, not silently discarded
  • The DLQ becomes your highest-signal alert: anything arriving there means something is genuinely wrong
  • You get a clean fix-and-reprocess workflow

A non-empty DLQ should page someone. An async system without a DLQ is silently losing work — or silently stuck.

DLQ Replay

DLQ replay is reprocessing messages after fixing the root cause.

Two things that go wrong in replays:

  1. Replaying too fast. Dumping 4,200 messages back at once produces exactly the burst that caused the failure. Throttle it, and replay a small batch first to confirm the fix.
  2. Replaying without idempotency. Some of those messages did partially succeed before failing. Without idempotent consumers, replay sends duplicate emails or double-charges cards. Idempotency is what makes DLQ replay safe.

13. Message Ordering

Here's a guarantee people assume they have and usually don't.

The moment you have more than one consumer, global ordering is gone.

Ordering and parallelism are in direct tension:

Partitioned ordering is what you actually want in almost every case:

This is the same idea as the shard key from Part 3, and it fails the same way: pick a partition key with poor distribution and you get a hot partition — one consumer overloaded while the rest idle.

The better design instinct: make ordering unnecessary. Version your events, use last-write-wins on a timestamp, or make each event carry enough state to be applied independently. Systems that don't need ordering are far easier to scale than systems that enforce it.


14. Eventual Consistency

Async processing means the system passes through states where different parts disagree.

The system is working exactly as designed. The user's experience is that it's broken.

This is eventual consistency — the same trade-off as replication lag in Part 3 and cache staleness in Part 4, appearing a third time. The pattern across the whole series:

Handling it well is mostly a product and UX problem:

That last point is the real rule. The failure isn't "we used a queue." The failure is "we used a queue for something the user was about to look at."


15. Failure Isolation and Recovery Semantics

Failure Isolation

The deepest reason to go async: it converts a shared failure domain into isolated ones.

Under synchronous composition, your availability is the product of your dependencies:

5 dependencies at 99.9% each → 0.999^5 = 99.5%
≈ 3.6 hours of downtime per month

Move four of them behind queues and the critical path's availability is governed by the two components that actually matter.

Every synchronous dependency multiplies into your availability. Every async dependency doesn't.

Recovery Semantics

Recovery semantics answer: what does the system do after a failure?

Every async system needs an explicit, written answer to five questions:

  1. Message lost? → Durable queue with persistence and replication
  2. Worker dies mid-job? → Visibility timeout, redelivery, idempotency
  3. Message processed twice? → Idempotency keys, or naturally idempotent operations
  4. Message always fails? → Retry limit, then DLQ, then alert
  5. Consumers can't keep up? → Queue depth alerting, autoscaling, backpressure, load shedding

If you can answer those five, you've designed an async system. If you can't, you've added a queue.


16. Putting It Together

The order flow from Part 3, redesigned end to end:

Every design decision, stated explicitly:

DecisionRationale
Order insert + payment stay syncThe user must know immediately whether it worked
Everything else is asyncThe user doesn't need it before the response
Queue is durable and boundedSurvives crashes; bounded gives you backpressure
Workers are idempotentAt-least-once delivery guarantees duplicates
Retries use exponential backoff + jitterAvoids retry storms and synchronized waves
Permanent errors go straight to the DLQNo point retrying a validation failure
DLQ is alerted on; replay is throttledFailures stay visible, recovery stays controlled
Partitioned by order_idPer-order ordering, parallel across orders
Autoscale on queue depth, with concurrency capsScale consumers without DDoSing the database
Load shedding drops analytics firstUnder stress, protect checkout

Response time went from 2,720ms to 900ms. But that's the least interesting result. The real result:


17. What Interviewers Are Actually Listening For

Phrases that signal production experience:

  • "Does the user need this before they get a response? If not, it's async."
  • "This is at-least-once, so the consumer has to be idempotent — I'd dedupe on a message ID."
  • "Exponential backoff with jitter — otherwise every failed message retries in the same instant."
  • "That's a permanent failure, not a transient one. Straight to the DLQ, no retries."
  • "I'd bound the queue. An unbounded queue just fails later and all at once."
  • "I'd cap worker concurrency — 200 workers would take the database down."
  • "The search index is eventually consistent, so I'd show a 'processing' state in the UI."
  • "I'd alert on the age of the oldest message, not just queue depth."

And the one that ends the conversation well: "What's our recovery story if the consumer has been down for two hours?"


18. Key Terms Recap

TermOne-line definition
Synchronous processingCaller waits for completion; failures propagate to the user
Asynchronous processingCaller returns early; work is handed off
ProducerWrites messages to the queue
ConsumerReads messages from the queue
QueueDurable buffer decoupling producers from consumers
WorkerProcess that executes the deferred job
Background jobA unit of work done outside the request path
DecouplingSeparation in time, rate and failure domain
BufferingHolding work when arrival exceeds processing capacity
Burst absorptionConverting a capacity problem into a latency problem
Queue depthUnprocessed backlog; the key health metric
Producer vs consumer rateTheir difference decides whether depth grows or drains
Consumer scalingHorizontal scaling of stateless workers
BackpressureDownstream signalling upstream to slow down
Load sheddingDeliberately dropping low-value work to protect critical work
RetriesReattempting transient failures
Exponential backoffDoubling delays so retry pressure decays
JitterRandomizing delays to break synchronization
Retry stormRetries amplifying a small fault into an outage
Worker failuresCrashes mid-job; handled by redelivery
Duplicate messagesGuaranteed under at-least-once delivery
At-least-once deliveryNever loses messages, may deliver more than once
IdempotencyProcessing twice yields the same result as once
Poison messageA message that fails every attempt
Dead Letter QueueHolding area for messages that exhausted their retries
DLQ replayThrottled reprocessing after the root cause is fixed
Message orderingLost with multiple consumers; use partitioned ordering
Eventual consistencyComponents temporarily disagree, then converge
Failure isolationOne dependency's outage doesn't become yours
Recovery semanticsThe explicit answer to "what happens after a failure?"

19. The Whole Series in One Frame

Five posts, one idea:

None of these is free. That's the point.

The engineer who says "add a load balancer, add Redis, add a queue, shard the database" has listed tools. The engineer who says "here's the constraint, here's the cheapest fix, and here's exactly what correctness I'm giving up to get it" has designed a system.

Three sentences to carry into every system design interview:

Find the constraint before you scale anything.

Every technique moves the bottleneck rather than removing it.

Name the trade-off out loud — that's what seniority sounds like.


20. Final Takeaway

Async processing is the most misunderstood tool in the set, because its most visible effect — a faster response — is its least important one.

What a queue actually gives you:

  • Decoupling — producers and consumers fail independently
  • Buffering — bursts become latency instead of lost work
  • Failure isolation — a third-party outage stops being your outage
  • Recovery semantics — work survives crashes and can be retried or replayed

What it costs you:

  • Eventual consistency — the user may see a system that hasn't caught up
  • Duplicates — at-least-once delivery makes idempotency mandatory
  • Lost ordering — parallelism and global order can't coexist
  • Operational surface — queue depth, DLQs, replays, backpressure, and alerts on all of it

Async doesn't make work faster. It makes work survivable.

If you're hiring backend engineers who can explain this under pressure, see companies.

That's the end of the series. Go find your constraint.


🎯 Now Go Explain This Out Loud

You can read every article on message queues, idempotency, backpressure and dead letter queues ever written and still freeze when an interviewer asks "what happens if your worker crashes halfway through?"

The gap between knowing and explaining under pressure is where most technical interviews are lost.

candidates.intervues.club — start a real AI voice interview right now. Speak your system design answer out loud, get a structured report on your reasoning, clarity and depth. Free to start.

intervues.club — hiring? Run AI voice technical interviews at scale, with candidates who can actually explain their architecture decisions.

No AI copilot feeding you answers. Just you, the question, and your ability to reason.

Practice the interview before the interview. → candidates.intervues.club


Part 5 of 5 in the System Design Fundamentals series. Part 1: Load, Latency and Throughput. Part 2: Horizontal Scaling and Bottleneck Migration. Part 3: The Database Is the Bottleneck. Part 4: Caching Isn't Just About Speed.

Tags: system design interview, asynchronous processing, message queues, producer consumer pattern, backpressure, load shedding, exponential backoff, jitter, retry storm, idempotency, at-least-once delivery, dead letter queue, DLQ replay, poison message, message ordering, eventual consistency, failure isolation, queue depth, background jobs, Kafka, RabbitMQ, SQS, worker scaling, distributed systems, scalable system design, backend engineering

Hey — it's Ayoush.

Nine years as an engineer, three interviewing candidates. I built Intervues because the gap isn't knowledge — it's saying what you know out loud. Real email at the other end: admin@intervues.club.

Keep reading

Rehearse the room. Then walk in ready.

An interview is the first day of the job — practice before it counts.

3 free credits · pay per interview · nothing recurring