Synchronous vs Asynchronous Processing
A checkout API can wait until every step finishes, or accept an order and complete work later. The first path gives the caller an immediate final answer but couples its latency and availability to every dependency. The second absorbs slow work behind a queue, but “accepted” no longer means “completed”—the system must expose status, retries, duplicates, and terminal failure.
This guide follows POST /orders with idempotency key ord-42. Success means inventory is reserved once, payment is authorized once, and the customer can discover the final result. We will run the order through both paths, crash a worker after charging but before acknowledging its message, and choose boundaries from the user's timing and consistency requirements.
The difference is when the caller gets control back
In synchronous processing, the caller waits while the callee performs enough work to return a final result. HTTP request-response is commonly used this way: the connection remains part of the operation's control path until success, failure, or timeout.
In asynchronous processing, the system durably accepts work, returns control before completion, and processes it later. A queue is common but not required; callbacks, polling, streams, and workflow engines can carry the eventual result. The initial response confirms only the boundary it actually crossed.
This is not “real-time vs background” or “HTTP vs messaging.” A producer may synchronously wait for a broker's publish confirmation and still have asynchronous business processing. Name each acknowledgment precisely: request received, message persisted, work started, or business outcome completed.
Quick reference
- Synchronous: caller waits for a final outcome or timeout.
- Asynchronous: caller gets an admission acknowledgment before completion.
- Transport choice does not define business completion semantics.
- A broker confirmation is not a consumer's successful outcome.
- Document what every response or acknowledgment proves.
Remember this
The design boundary is the acknowledgment: state exactly whether it proves admission, durable enqueue, or completed business work.
Synchronous path: one deadline covers every dependency
The synchronous order handler validates the request, reserves inventory, authorizes payment, writes the order, and only then returns 201 Created. The client gets a clear final answer and errors can propagate directly. Tracing is linear because every span belongs to one active request.
The cost is temporal coupling: inventory and payment must both be available and fast inside the caller's deadline. If the API allows 2 seconds, it cannot safely give two dependencies 2 seconds each; each hop needs a smaller budget that leaves time for local work and the response. Unbounded retries multiply load and can turn one slow dependency into a cascading failure.
A client timeout creates an ambiguous outcome. Payment may have succeeded just as the connection closed. The client must retry with the same idempotency key, and the server must return the recorded outcome instead of charging again.
Quick reference
- Final response can represent the completed operation.
- Every dependency consumes the same end-to-end deadline.
- Slow or unavailable downstream services affect the caller directly.
- Timeout does not prove rollback; outcomes can be ambiguous.
- Idempotency makes a retry retrieve/reuse the original outcome.
Remember this
Synchronous flow is simplest when the final answer fits one deadline; idempotency is still required because a timeout can hide a successful side effect.
Asynchronous path: accept work, then expose its lifecycle
The asynchronous API validates enough to admit the request, creates operation ord-42, durably publishes OrderRequested, and returns 202 Accepted with a status URL. RFC 9110 defines 202 as noncommittal: processing is not complete and may still fail. The response therefore must not say “order placed” unless that business promise is already true.
A worker later reserves inventory, authorizes payment, and records SUCCEEDED or a terminal failure. The client polls GET /operations/ord-42, receives a callback, or consumes an event. Queue depth absorbs bursts, but waiting has moved from an HTTP connection into backlog age. Capacity planning must track arrival rate, processing rate, and oldest-message age.
Do not publish a message after committing an unrelated database transaction and assume both happened. A crash between those writes can create an order with no event. Use a transactional outbox when database state and message publication must become visible together.
Quick reference
202 Accepteddoes not guarantee eventual success.- Return a stable operation ID and status/result location.
- Persist terminal failure instead of leaving work pending forever.
- Queue backlog converts traffic spikes into delayed completion.
- Use an outbox to close database-commit/publish gaps.
Remember this
Asynchronous admission is a promise to track work, not a claim that it succeeded; expose durable status and every terminal outcome.
One order through both paths
For ord-42, the synchronous client stays connected across API → inventory → payment → database. The critical-path latency is approximately the sum of serialized work plus network overhead; parallel independent calls can reduce elapsed time but complicate cancellation and partial failure.
The asynchronous client waits only through validation, operation creation, and durable enqueue. Worker time is outside the initial response, so request latency falls while completion latency may rise with queue wait. Reporting only the fast 202 latency hides what the user cares about.
Measure synchronous paths with end-to-end success latency, timeout rate, and dependency budgets. Measure asynchronous paths with admission latency, enqueue failures, queue depth, oldest-message age, processing latency, redelivery count, terminal failure rate, and end-to-end time from acceptance to completion.
Quick reference
- Synchronous critical path includes downstream execution.
- Asynchronous request path ends at durable admission.
- Fast acceptance can coexist with a very slow completion.
- Trace with the same correlation/order ID across HTTP, queue, and worker.
- Measure user-visible end-to-end time, not only API response time.
Remember this
Async reduces waiting on the admission request, not necessarily the user's completion time—measure both as separate SLIs.
Failure story: charged, then redelivered
Trigger. A worker receives OrderRequested(ord-42), authorizes payment, then crashes before sending its consumer acknowledgment. Symptom: the broker redelivers the unacknowledged message and a second worker attempts another charge. Root mechanism: at-least-once delivery preserves work through consumer failure by allowing redelivery; it does not provide exactly-once business effects.
Recovery. The second worker uses ord-42 as the payment idempotency key. The payment service returns the original authorization, the worker records the order outcome, and only then acknowledges the message. If an effect cannot be made idempotent, store an inbox/deduplication record in the same transaction as the local state change.
Poison messages need bounded attempts and a dead-letter path with ownership, alerting, and replay tooling. Endless immediate requeue creates a hot loop. A dead-letter queue without a recovery process merely hides failed orders.
Quick reference
- Crash after effect but before ack causes redelivery.
- Redelivery means consumers must tolerate duplicates.
- Use the business operation ID as an idempotency key.
- Acknowledge only after the required durable local outcome.
- Bound retries and operate dead-letter recovery explicitly.
Remember this
At-least-once messaging protects delivery by permitting duplicates; idempotent effects turn redelivery into recovery instead of double execution.
Reliability requires acknowledgments on both sides
Reliable publication and reliable consumption are separate. A publisher confirm tells the producer that the broker accepted responsibility according to its configured durability semantics. A consumer acknowledgment tells the broker that the delivered work was processed far enough to remove it. One does not imply the other.
If the publisher loses its connection before receiving a confirm, it cannot know whether the broker accepted the message. Republish may duplicate it; dropping it may lose it. Use a stable message ID and idempotent consumer. Likewise, if a consumer connection closes before acknowledgment, the broker may redeliver.
Ordering is scoped, not universal. Multiple workers, retries, and partitions can reorder events. If OrderCancelled must not run before OrderCreated, route related events through an ordering boundary or attach an aggregate version and reject/defer stale transitions.
Quick reference
- Publisher confirm: broker accepted the publication.
- Consumer ack: broker may remove the delivery after processing.
- Uncertain confirms require safe republish and deduplication.
- Redelivery is expected after consumer/network failure.
- Preserve required ordering by key/partition or validate versions.
Remember this
Confirm publication and acknowledge consumption separately; stable IDs and idempotency resolve the uncertainty between either acknowledgment and a network failure.
Choose the boundary from the user promise
Use synchronous processing when the caller needs the final answer before continuing, the work reliably fits a bounded deadline, and immediate errors are actionable—for example, reading a profile or validating a small command.
Use asynchronous processing when work is long-running, bursty, retryable, fan-out heavy, or need not finish before the caller proceeds—for example, video encoding, report generation, and notification delivery. Provide status and cancellation when the user still owns the outcome.
Most production flows are hybrid. Checkout may synchronously validate the cart and establish payment authorization, commit an order, then asynchronously send receipts and analytics. Keep correctness-critical steps inside the acknowledged contract; move side effects only when delayed or failed completion is acceptable and visible.
Quick reference
- Need final result now + bounded work → synchronous.
- Long/bursty/retryable work → asynchronous with durable status.
- Do not async a step whose failure invalidates the response promise.
- Hybrid flows keep core invariants before acknowledgment.
- Choose per operation, not once for an entire service.
Remember this
Acknowledge only what is already guaranteed; move work behind the boundary when delayed completion is acceptable, observable, and recoverable.
Key takeaway
Synchronous processing keeps the caller on the execution path and can return a final result, but every dependency consumes one deadline and can propagate failure. Asynchronous processing separates admission from completion, absorbs bursts, and enables independent recovery, but introduces operation state, backlog, duplicate delivery, and terminal-failure handling. The correct design starts with the user promise, then places the acknowledgment exactly where that promise becomes true.
Practice (35 minutes): implement POST /orders twice. First call a worker synchronously with a 1-second end-to-end deadline and retry the same ord-42 after a forced client timeout. Then return 202 with /operations/ord-42, process a queued message, and crash the worker after its mock charge but before acknowledgment. Recover through redelivery using the same idempotency key. You pass when one charge exists, the operation reaches a terminal state, and your dashboards distinguish admission latency from acceptance-to-completion time.
Related Articles
Explore this topic