Definition
- Synchronous communication : the caller sends a request and blocks waiting for the response (REST, gRPC), giving immediate results but tight coupling.
- Asynchronous communication : the caller sends a message and continues without waiting (Kafka, SQS), giving decoupling and resilience but eventual rather than immediate results.
- Cascading failure : when a slow sync dependency ties up the caller's threads and brings down the whole chain.
- Circuit breaker : a pattern that stops calling a failing service after a threshold, returning a fallback instead of blocking.
When Rajesh asks a teller for his balance, he waits at the counter until the teller answers : that is synchronous. When Rajesh drops a loan application in a box and leaves, the loan department processes it later : that is asynchronous. National Bank of India uses conversations when the customer needs an answer right now, and drop-boxes when the work can happen in the background.
Understanding the Concepts
What is synchronous communication?
Synchronous calls using REST or gRPC are request-response: the caller blocks until it gets a reply or times out. This is simple to reason about and ideal when the caller genuinely needs the result to continue.
package com.bank.communication;
import java.math.BigDecimal;
public class SyncBalanceClient {
public BigDecimal getBalance(String accountId) {
var resp = restClient.get()
.uri("/accounts/{id}/balance", accountId)
.retrieve().toEntity(BigDecimal.class); // caller WAITS here
return resp.getBody();
}
}
What is asynchronous communication?
Async messaging using Kafka or SQS is fire-and-forget or publish-subscribe. The caller sends a message to a broker and continues immediately, while consumers process it later.
package com.bank.communication;
public class AsyncTransferPublisher {
public void onTransferCompleted(Transfer t) {
kafkaTemplate.send("transfer.completed", t.accountId(), toJson(t));
// Caller continues; receipts, fraud, analytics happen later
}
}
What are the trade-offs?
| Aspect | Synchronous | Asynchronous |
|---|---|---|
| Coupling | Tight (both must be up) | Loose (decoupled by broker) |
| Result | Immediate | Eventual |
| Failure mode | Cascading | Buffered |
| Complexity | Simple | Needs idempotent consumers |
| Use case | Balance read, auth | Notifications, analytics |
When should you use sync?
Use synchronous when the caller needs an immediate answer to proceed and the operation is a query or short command, such as authentication, balance read, or payment authorization.
When should you use async?
Use asynchronous when work can happen in the background, when you want to decouple producers from consumers, when you need to buffer spikes, or when one event must trigger many independent reactions.
How do real systems mix both?
The synchronous path does the minimum needed to answer the user correctly, and everything deferrable is pushed onto an asynchronous path. For a transfer: commit the money synchronously, publish a transfer-completed event, return success, and let receipt, fraud check, and analytics run asynchronously.
package com.bank.service;
import java.math.BigDecimal;
public class HybridTransferService {
public TransferResult transfer(String from, String to, BigDecimal amount) {
// SYNC: commit the money (must be immediate and correct)
ledger.debitCredit(from, to, amount);
// ASYNC: everything else can happen in the background
eventBus.publish("transfer.completed",
Map.of("from", from, "to", to, "amount", amount));
return TransferResult.success(); // return fast
}
}
How do you protect sync edges?
Protect synchronous calls with timeouts, retries with exponential backoff, and circuit breakers so a slow dependency cannot cascade and bring down the whole system.
package com.bank.resilience;
public class CircuitBreakerExample {
@CircuitBreaker(name = "account-service", fallbackMethod = "fallback")
public BigDecimal getBalance(String accountId) {
return accountClient.getBalance(accountId); // may timeout
}
public BigDecimal fallback(String accountId, Exception e) {
return cachedBalance.get(accountId); // serve cached on failure
}
}
Why must async consumers be idempotent?
Async delivery is typically at-least-once, meaning a message can be redelivered if the consumer crashes before acknowledging. Consumers must handle duplicates gracefully by checking a message id.
What should a senior engineer know?
Designing which work is synchronous and which is asynchronous is one of the highest-leverage decisions in a distributed system. It directly determines both how fast the user-facing response feels and how resilient the system is to partial failure.
Visual Overview
Hybrid Sync + Async Architecture
┌──────────┐ sync (REST) ┌──────────────┐
│ Client │────────────────►│ Transfer │
│ (Rajesh)│◄────────────────│ Service │
└──────────┘ fast response └──────┬───────┘
│ publish event (async)
▼
┌───────────────┐
│ Kafka │
│ (broker) │
└──┬────┬────┬──┘
│ │ │
┌──────▼┐ ┌─▼───┐ ┌▼────────┐
│Receipt│ │Fraud│ │Analytics│
│Service│ │Check│ │Service │
└───────┘ └─────┘ └─────────┘
(async) (async) (async)
Sync path: caller blocks, gets immediate result
Async path: caller returns fast, work happens later
Protection: timeout -> retry -> circuit breaker
Key Takeaway
Synchronous and asynchronous communication are two ways services talk, and I choose between them by the nature of the interaction. Synchronous REST or gRPC is request-response where the caller blocks until it gets a reply. It is simple and right when the caller genuinely needs the result, like reading a balance. Its weakness is tight coupling and cascading failures.
Asynchronous messaging through Kafka or SQS decouples services in time. The producer sends a message and continues immediately while consumers process later. This buffers bursts and means a slow downstream never blocks the fast upstream.
In practice at National Bank of India, I make the sync path do the minimum needed : commit the money, return success. Everything deferrable : receipt, fraud check, analytics : goes async. This both speeds up the user response and keeps the transfer resilient.
The senior insight is that this is deliberately a mix. Sync calls form thin, latency-sensitive edges protected with timeouts, retries, and circuit breakers. Async messaging carries everything deferrable. Designing which work is sync and which is async is one of the highest-leverage decisions in a distributed system.
Remember one sentence: sync for immediate answers the user needs, async for everything deferrable, and protect sync edges with circuit breakers so nothing cascades.
---