← All posts
6 min read

Synchronous and Asynchronous Work: When Must the Caller Wait?

#technology#distributed-systems#asynchronous-processing#architecture
📑 On this page

Some work must finish before a caller can continue. Other work only needs to be accepted safely and completed later.

The choice changes user feedback, failure handling, system coupling, and what a successful response actually promises.

Synchronous work returns its result in the active interaction; asynchronous work returns before the final result exists.

Asynchronous design can improve responsiveness and absorb bursts, but it creates a workflow that must be operated explicitly.

A concrete example: processing a video

A user uploads a large video.

The application quickly:

  1. validates the upload,
  2. stores the original durably,
  3. creates a processing job,
  4. returns a job identifier,
  5. and shows "Processing."

Workers later create several resolutions, thumbnails, and captions. The user can leave and return while status continues independently.

Synchronous work

In a synchronous interaction, the caller waits for completion or failure.

Examples include:

  • calculating a cart total,
  • validating a password,
  • reading a profile,
  • or reserving inventory needed for checkout.

The simple control flow makes result handling immediate, but long work holds open resources and ties caller latency to every dependency.

Asynchronous work

In an asynchronous interaction, the system acknowledges work before final completion.

Examples include:

  • generating a report,
  • transcoding media,
  • importing a large file,
  • sending bulk email,
  • or training a model.

The caller receives acceptance, not the finished result.

Accepted is not completed

An API may return 202 Accepted to mean:

The request was accepted for processing.

It must not present this as final success. The work may later fail validation, exhaust retries, be canceled, or lose access to a dependency.

Product language should distinguish queued, running, completed, failed, and canceled.

Why move work out of the request?

Asynchronous processing helps when work:

  • takes longer than a practical request deadline,
  • can survive temporary dependency failure,
  • arrives in bursts,
  • needs expensive specialized workers,
  • or does not affect the immediate next user action.

It also protects front-facing capacity from long-running jobs.

When synchronous is better

Async is not automatically more scalable or modern.

Keep work synchronous when:

  • the caller needs the answer immediately,
  • processing is short and reliable,
  • later status would complicate the experience,
  • or acceptance without completion has little value.

Turning a simple validation into a background job creates unnecessary state and delay.

Durable acceptance

Before acknowledging async work, the system should make it durable enough to survive a process crash.

That may mean:

  • committing a job record,
  • placing a message in a durable queue,
  • or storing an outbox entry in the same transaction as related state.

Returning "accepted" while the job exists only in memory can silently lose work.

Job identity

Each job needs a stable identifier.

The identifier supports:

  • status lookup,
  • deduplication,
  • cancellation,
  • logs and traces,
  • retries,
  • notifications,
  • and support investigation.

It should be scoped and authorized so one user cannot inspect another user's job.

State machines

Async workflows need explicit states and allowed transitions.

For example:

queued -> running -> completed
                  -> failed
queued -> canceled
running -> cancel_requested -> canceled

A state machine prevents impossible combinations such as completed and retrying simultaneously.

Status APIs

A caller may poll:

GET /jobs/8472

The response can include:

  • state,
  • progress when measurable,
  • result location,
  • failure reason safe for the user,
  • and retry or cancellation options.

Polling intervals should avoid unnecessary load and may increase while work remains unchanged.

Notifications

Instead of requiring constant polling, completion can be signaled through:

  • in-app updates,
  • WebSocket or server-sent events,
  • push notification,
  • email,
  • or webhook.

Notifications are best-effort signals. The durable job status remains authoritative if a notification is delayed or lost.

Progress

Progress is useful only when it is truthful.

For known work units, report determinate progress such as 35 of 100 files. For unpredictable work, show stages or an indeterminate indicator.

Fake percentages that stall at 99 percent reduce trust.

Retries

Workers should distinguish transient and permanent failures.

Retry transient failures with bounded backoff and jitter. Preserve attempt count and last error. Move repeatedly failing work to a visible failure state or dead-letter queue.

Jobs must be idempotent because a worker can crash after doing the effect but before acknowledging completion.

Duplicate jobs

A user can submit twice or a producer can retry after uncertain acceptance.

Use:

  • client operation identifiers,
  • idempotency keys,
  • unique business constraints,
  • or deduplication windows.

Duplicate protection may apply to job creation and again inside the job's side effects.

Cancellation

Cancellation is a request, not instant reversal.

A queued job may be easy to remove. A running job needs cooperative checkpoints. An external side effect may already be irreversible.

Define whether cancellation means:

  • do not start,
  • stop future stages,
  • attempt compensation,
  • or merely hide the result.

Ordering

Async jobs can complete in a different order from submission.

Two profile imports may race, causing older data to overwrite newer data. Solutions include:

  • per-entity ordering,
  • version checks,
  • cancellation of obsolete work,
  • or applying only the latest requested version.

Do not assume queue order equals completion order.

Backpressure

If producers create work faster than consumers finish it, backlog grows.

Backpressure controls demand through:

  • queue limits,
  • admission control,
  • rate limits,
  • slower producers,
  • scaling consumers,
  • or rejecting low-priority work.

A queue delays overload; it does not provide infinite capacity.

Priority and fairness

Some jobs are urgent, but strict priority can starve ordinary work.

Systems may use:

  • separate queues,
  • weighted scheduling,
  • quotas per customer,
  • age-based promotion,
  • or reserved worker capacity.

Priority policy is a product and fairness decision, not only a technical number.

Observability

Measure:

  • queue age,
  • time to start,
  • processing duration,
  • completion rate,
  • retry rate,
  • failure reason,
  • cancellation delay,
  • and backlog by job type.

User-visible latency includes waiting before a worker starts, not only execution time.

Knowledge check

  1. What does accepted mean in an asynchronous API?
  2. Why must job creation be durable before acknowledgment?
  3. Why should notifications not be the only source of job status?
  4. What makes cancellation cooperative rather than instant?
  5. How can older async work overwrite newer intent?

The one idea to remember

Synchronous work completes inside the caller's wait; asynchronous work returns after durable acceptance and finishes later. Async design improves decoupling only when job identity, status, retries, idempotency, cancellation, backpressure, and user feedback are treated as first-class behavior.