Synchronous and asynchronous processing, queues, and workers

~9 min

Erik taps Place order.

The backend must validate the order, charge his card, and store the result, and it must also email a receipt, text the café, update his loyalty points, and record the order for analytics.

That is at least seven jobs.

Here is the architectural question. Should Erik stare at a spinner while all seven happen?

The answer leads to one of the most useful distinctions in system design: synchronous versus asynchronous processing.

What is synchronous processing?

Work is Concept · lights on your mapsynchronous processingWork performed while the requester waits, because the answer must come back before the response is honest. The opposite of asynchronous, and the reason a spinner exists. when it happens while the requester waits for the result.

The request-and-response conversations you traced in Module 4 are synchronous by nature. The client sends a request, waits, and uses the response.

Some work must be synchronous.

Before the app can honestly display “order confirmed,” the backend must know that the order was valid, that the card was charged, and that the order was saved.

If any of those fail, the answer changes. Erik needs to know now, not in an email an hour later.

A useful test is: must this answer come back before the response to the user is honest?

What is asynchronous processing?

Work is Concept · lights on your mapasynchronous processingWork accepted now and performed soon, with nobody waiting on it. The opposite of synchronous; sorting each job into the right column is a core design skill. when the system accepts it now but performs it later, without making the requester wait.

The receipt email can arrive forty seconds after Erik’s spinner is gone, the loyalty points can appear a minute later, and the analytics record can be written whenever convenient. And nobody suffers.

None of those results change what “order confirmed” means, so none of them should hold the confirmation hostage.

Sorting the seven jobs looks like this:

One tap, sorted into now and later
Erik taps Place order
Now · synchronous · Erik waits
validate the ordercharge the cardstore the order
Later · asynchronous · nobody waits
email the receipttext the caféupdate loyalty pointsrecord for analytics
Fig. 3 — seven jobs from one tap

The synchronous column determines how long the spinner lasts, while everything moved to the asynchronous column makes the product feel faster without removing any work.

The machinery for the “later” column is two components that appear on nearly every serious architecture diagram: the queue and the worker.

What is a queue?

A Concept · lights on your mapqueueA waiting line for work: components place messages describing tasks in one end, and the messages wait until something takes them out the other. Decouples the asker from the doer. The backend drops the task and answers the user immediately. AWS’s managed queue is SQS. is a waiting line for work.

One component places a message into the queue, a small piece of data describing a task, and it waits there until another component takes it out and acts on it.

A message might say: { "task": "send_receipt", "order_id": 1042 }

After finishing the synchronous work, the coffee backend drops messages for the receipt, the café text, the loyalty update, and the analytics record into the queue, then immediately answers Erik. Dropping them takes a few milliseconds, trivial next to the work itself.

Messages generally wait briefly. Under normal load a message may sit in the queue for milliseconds, and when things are busy it may wait minutes.

Most queues deliver messages in roughly the order they arrived, but many real-world queues do not guarantee strict ordering, so systems that truly require ordering must choose queues and designs that provide it.

In Module 8, you met a managed example on AWS’s shelf: Amazon SQS, the Simple Queue Service. This lesson is what that service is for.

What is a worker?

A Concept · lights on your mapworkerA program whose job is taking messages from a queue and performing them, with no user waiting and no request to answer. It can take its time, retry failures, and crash without any spinner noticing; a failed message can return to the queue for another attempt. is a program whose job is taking messages from a queue and performing the work they describe.

A worker is still backend software, often the same kind of code, running on the same kind of infrastructure. What changes is its relationship to time.

No user is waiting on a worker, and that changes everything about it:

  • It can take seconds or minutes per task without harming anyone’s experience
  • It can retry a failed task calmly, with backoff, using Module 6’s discipline
  • If it crashes mid-task, the message can return to the queue and be attempted again — by this worker or another one
  • More workers can be added when the line grows, and removed when it shrinks

That last point connects directly to Module 8. Queue depth, meaning how many messages are waiting, is one of the classic autoscaling signals. A long line calls for more workers, and a short line for fewer.

Interactive — one tap, two speeds
Synchronous
validate · charge · store (user waits ~1s)
The queue
“receipt for 1042” · “text the café” · “+10 points”
Workers, seconds later
email sent · SMS sent · points updated
You tapped Place order. Watch the work split into now and later.

The queue is a shock absorber

This design has a second benefit, and it is the one that saves companies on their biggest days. Suppose the dinner rush triples the order volume, and the workers cannot keep up.

The queue simply grows.

Receipts run a few minutes late and loyalty points appear a little slowly, but nothing breaks, no order is lost, and no user sees an error.

Compare that with the fully synchronous alternative. Every spinner would carry all seven jobs, and at the moment of maximum business, spinners would start timing out.

A useful principle is that a growing queue is not the failure but the system bending instead of breaking.

The queue’s length, often called the backlog or queue depth, becomes a health signal worth watching. A line that grows briefly during a rush is the design working. A line that grows without ever shrinking means the workers have fallen behind for good, and someone should be paged.

What asynchronous work costs

Moving work behind a queue is powerful. But it is not free.

The honest fine print looks like this:

  • “Done” becomes “pending.” The user was told the order is confirmed before the receipt exists. The product must be designed so that in-between state makes sense — statuses, placeholders, and updates that arrive when the work completes.
  • Messages can be delivered more than once. Many queues promise at-least-once delivery: no message is lost, but a message may occasionally arrive twice. Workers must therefore be idempotent — Module 6’s twice-equals-once discipline, reporting for duty inside the company’s own systems.
  • Failures happen out of sight. A synchronous failure shows the user an error. An asynchronous failure happens quietly in a worker, so the team needs monitoring to notice it.
  • Some messages never succeed. A malformed message will fail every retry, forever. Queues commonly move such messages to a dead-letter queue — a separate holding line where repeatedly failing messages wait for engineers to investigate — instead of letting them clog the main line.

None of this argues against asynchronous design. It defines the work of doing it well.

The target sentence, with the machinery visible

Read the target sentence again:

“…publishes an event to a queue, and an asynchronous worker processes the event and sends a notification.”

You can now see the architecture inside every word.

The backend did not send the receipt. It published a message describing the task and walked away, and a worker, running on its own schedule with no user waiting, picked the message up and sent the notification.

The sentence has been describing this exact design all along. The one word this lesson has not yet formally explained is “event”, and that is the next lesson’s job.

The mental model to remember

Synchronous work happens while the requester waits, because the answer must come back before the response is honest.

Asynchronous work is accepted now and performed soon, without holding anyone’s spinner.

A queue is a waiting line for messages describing work. It decouples the component asking for work from the component doing it.

A worker takes messages from the queue and performs them, able to take its time, retry idempotently, and fail without any user noticing.

The queue acts as a shock absorber. Under load it grows instead of breaking, and its depth is both an autoscaling signal and a health signal.

The costs are real but manageable: pending states, at-least-once delivery, invisible failures, and dead-letter queues for messages that will never succeed.

You should now be able to look at any piece of backend work and make the architect’s call: does anyone need this answer before the response is honest — or can it go in the queue?

Check — then the lesson continues

A PM wants to add “generate a shareable order image” to checkout, and it takes ~8 seconds of processing. Where does that work go?

▼ answer the check to continue ▼