DSA Guide
System Design

Queues and Async Work

Decoupling slow work from fast responses, delivery guarantees, back pressure, and why every consumer must be idempotent

A queue lets a request finish before its work does.

The user uploads a photo. Resizing it into five formats takes eight seconds. Without a queue, they stare at a loading spinner for eight seconds. With one, they get a response in 50 ms and the resizing happens behind the scenes.

What a Queue Buys You

Moving slow work off the request path
uploadenqueue jobClientAPI ServerQueuedurableWorker 1Worker 2Worker 3
ClientServiceQueue
response time50 msqueue depth1
The API stores the file, drops a job on the queue, and replies immediately. The user is not waiting for the resize.
1 / 4
Four benefits in one picture: fast responses, spike absorption, independent scaling, and isolation from worker failure.
BenefitWhat it means
Fast responsesThe user waits for the enqueue, not the work
Spike absorptionThe queue buffers; the workers catch up
Independent scalingAdd workers without touching the API
Failure isolationA worker crash retries the job rather than failing the request

What a queue costs

The work is now eventual. The user is told "upload received", not "upload processed". The interface must reflect that — a progress state, a notification, something.

Failures become invisible. A failed request returns a 500 the user sees. A failed job fails silently at 3 a.m. unless you are monitoring the dead-letter queue.

Ordering is not guaranteed unless you arrange it.

Debugging is harder — the work happens somewhere else, later.

What Belongs on a Queue

Good fitWhy
Sending email and notificationsSlow, and nobody is watching
Image and video processingVery slow, CPU-heavy
Report generationSlow, not urgent
Search index updatesSlight delay is invisible
Analytics eventsEnormous volume, no user waiting
Third-party API callsTheir downtime should not be yours
Poor fitWhy
Anything the response depends onThe user needs it now
AuthenticationMust be immediate and consistent
ReadsThere is nothing to defer

Delivery Guarantees

This is the part with real consequences.

GuaranteeMeansReality
At most onceNever duplicated, may be lostRarely acceptable
At least onceNever lost, may be duplicatedThe practical default
Exactly onceNever lost, never duplicatedVery hard, often faked

At-least-once is what you will actually get

A worker takes a job, completes it, and crashes before acknowledging it. The queue never heard the acknowledgement, so it hands the job to another worker.

The work happens twice. This is not a bug in the queue — it is the only safe behaviour available, because the queue cannot tell "finished but did not ack" from "died halfway".

Therefore: every consumer must be idempotent. Not as a nicety — as a requirement.

NOT idempotent:  balance = balance + 10        ← runs twice, adds 20
Idempotent:      if not processed(job_id):
                     balance = balance + 10
                     mark_processed(job_id)

"Exactly once" systems achieve their guarantee by doing exactly this — deduplicating on an id — rather than by preventing redelivery.

Back Pressure

If producers are faster than consumers, the queue grows without limit. Eventually it runs out of memory or disk, and then it fails at the worst possible moment.

ResponseWhat happensSuits
Scale consumersDrain fasterThe default, if the work parallelises
Reject new workProducers get an error and back offProtects the system honestly
Drop by priorityShed low-value work firstAnalytics before payments
Slow the producersRate-limit at the front doorWhen clients can retry

Queue depth is the metric to watch

A queue that is usually near empty is healthy — consumers keep up.

A queue with steadily rising depth is a system already failing; it just has not noticed yet. The alert should fire on the trend, not on the absolute number.

The useful question in any design with a queue: "what happens when consumers cannot keep up?" An answer of "the queue grows" is incomplete.

Dead-Letter Queues

Some jobs will never succeed — malformed input, a deleted record, a bug. Retrying them forever wastes capacity and hides the problem.

attempt 1  →  fail  →  retry in 1s
attempt 2  →  fail  →  retry in 4s
attempt 3  →  fail  →  retry in 16s
attempt 4  →  fail  →  move to DEAD LETTER QUEUE

The dead-letter queue is where broken jobs go to be looked at by a human. Alert on it. A silently filling dead-letter queue is lost work nobody knows about.

Note the growing gaps — exponential backoff. Retrying immediately hammers a system that is probably already struggling.

Queue or Log

Two shapes, often confused.

Message queue (SQS, RabbitMQ)Event log (Kafka)
After consumptionThe message is removedThe event stays for days
ConsumersOne consumer gets each messageMany consumers read independently
ReplayNot possibleRead from any past position
OrderingWeak or per-groupStrict within a partition
FitsTask distributionEvent streams, multiple subscribers

The deciding question: does more than one thing care?

"Resize this image" — one worker does it, then it is done. That is a queue.

"A user just signed up" — the email service, the analytics pipeline and the recommendation engine all care, and next month a fourth service will too. That is a log.

Retention is the other half: a log lets you fix a consumer bug and replay the last three days. A queue does not.

Ordering

Most queues do not guarantee order. Two workers pulling concurrently will finish in whatever order they finish.

When order matters — "created" must be processed before "updated" — the standard fix is partitioning by key: all events for user 42 go to the same partition, and one consumer handles that partition in order.

Ordering costs parallelism

One consumer per partition means the throughput of a single key is the throughput of one consumer. Partition by a key with many values, or that consumer becomes the bottleneck.

If order does not truly matter, do not ask for it. Many "ordering" requirements dissolve if events carry timestamps or version numbers and consumers ignore stale ones.

  • Consistency and CAP — idempotency, and why retries make it mandatory
  • Reliability — backoff, circuit breakers and timeouts
  • Scaling — queues as the step that keeps responses fast
  • News Feed — fan-out on write, which is a queue-driven design

On this page