intermediate 2 min answer

A team debates whether an operation should be synchronous or asynchronous. What decides it, and what is the cost of getting it wrong in each direction?

linearsyncasyncuser-experiencecoupling
Show the full answer Hide the answer

What decides it

Whether the caller needs the result to proceed.

  • Synchronous when the caller cannot continue without the outcome: a validation that determines whether the next screen appears, a payment authorisation that determines whether the order is accepted, a permission check.
  • Asynchronous when the caller only needs the work to be accepted: sending a notification, updating an index, generating a report, propagating a change to a derived store.

The question is not "is it slow" — a slow operation the caller needs is still synchronous, and the fix is to make it faster or to change the product's promise.

The cost of getting it wrong

Synchronous when it should be asynchronous: the caller's availability becomes the product of every dependency's availability, latency accumulates, and a slow downstream becomes a user-visible failure. This is the more common error and it is what produces fragile request paths.

Asynchronous when it should be synchronous: the user is told something succeeded that has not happened yet, and the failure surfaces later with no context. Worse, the system now has an intermediate state that the product must express — and if it does not, the user sees an inconsistency and reports a bug.

The middle position that is frequently correct

Accept synchronously, complete asynchronously, and make the intermediate state visible. The caller gets a durable acknowledgement immediately — the request is recorded and will happen — and the product shows "processing" rather than claiming completion.

That requires the product to have a concept of pending, which is a design decision rather than an engineering one and is the part that gets skipped.

The local-first variant

For a product where the client holds state, the operation is synchronous against the local store and asynchronous against the server. The user sees an immediate result, and the sync engine reconciles.

That is an excellent user experience and it requires a conflict policy per field, an authority rule for reconnection, and a way to surface a rejected change — none of which is optional, and all of which is frequently discovered after the optimistic update was shipped.