Skip to main content
foundations

Sync vs async communication

9 min read
Fully authored

When to make the caller wait, and when to fire-and-forget.

Every service-to-service interaction is either synchronous (caller waits for response) or asynchronous (caller sends a message and moves on). The choice affects everything: coupling, failure modes, latency budgets, complexity, monitoring, retries.

Most engineers default to synchronous — it's simpler to reason about. But asynchronous is the tool for eliminating cascading failures, absorbing spikes, and decoupling teams. Every serious system has both. Learning which to use where is a Staff-level skill.

Sync vs async at a glance

DimensionSyncAsync
Caller behaviorWaits for responseSends message and returns immediately
Latency of caller pathSlowest step in chainTime to enqueue only (~1ms)
CouplingTight — caller depends on callee availabilityLoose — buffered by queue
Failure propagationCascades up the call chainAbsorbed by queue; retryable
Load absorptionDownstream must scale with loadQueue buffers spikes for slow downstream
DebuggingEasier — linear call stackHarder — logs scatter across time
RetriesCaller retries with backoffBroker retries; DLQ for failures
When to useUser-facing responsesBackground work, notifications, spikes

The failure modes are different

Sync failure mode
Payment service is slow. API waits. Users start seeing 30s response times. Their retries pile up. Now every service in the chain is holding threads. Thread pool exhaustion. Cascading failure.
Fix: circuit breakers, timeouts, bulkheads.
Async failure mode
Consumer is slow. Queue depth grows. If growth exceeds retention, older messages get dropped. Users report "my email never came." Silent staleness.
Fix: monitor queue depth, autoscale consumers, dead-letter queues.

When to pick which — the checklist

Is the user waiting for this action to finish?

Applied in real systems

Key takeaways

  • Sync: caller waits. Simple. Cascading failures on downstream slowness.
  • Async: fire-and-forget via queue. Decoupled. Failure isolation. Adds latency + eventual-consistency complexity.
  • Use sync for user-facing responses — user is waiting. Use async for background work, batch processing, notifications.
  • Async buffers spikes. Producers keep sending even when consumers are slow.
  • Every serious system has both. gRPC/REST for user requests, Kafka/SQS for internal fan-out.

References

  • Kleppmann (2017) — DDIA, Chapter 11.
  • Kafka documentation and books by Neha Narkhede et al.

Practice what you just read

Every foundation concept has a companion quiz to close the loop.