Choosing a Communication Style
Why this matters: "we'll use gRPC" answers the wrong question. The decision that shapes your architecture is synchronous or asynchronous — whether the caller waits. Protocol comes second.
Key takeaway
Synchronous calls give you a simple mental model and an immediate answer, at the cost of temporal coupling: the callee must be up right now. Asynchronous messaging decouples in time and absorbs load spikes, at the cost of complexity and eventual consistency. Choose per interaction, not once per company.
The fundamental split
A useful test: would a human wait for this? Checking out needs the payment authorized before the page can respond — synchronous. Sending the receipt email, updating the recommendation model, and refreshing the analytics rollup do not — asynchronous, every one.
The four options
| Style | Shape | Strengths | Weaknesses | Reach for it when |
|---|---|---|---|---|
| gRPC | Sync, binary, HTTP/2 | Fast, typed contract, streaming, small payloads | Not browser-native, harder to eyeball on the wire | Internal service-to-service at volume |
| REST / HTTP+JSON | Sync, text, HTTP/1.1+ | Universal, cacheable, trivially debuggable | Verbose, over- and under-fetching, no schema by default | Public APIs, partner integrations, browser clients |
| GraphQL | Sync, client-specified query | Client fetches exactly what it needs in one round trip | Server complexity, caching is hard, N+1 queries | Many diverse clients over one rich data graph |
| Messaging / events | Async via a broker | Decoupled, buffers spikes, multiple consumers, replayable | Eventual consistency, ordering and duplicate handling | Fan-out, background work, spike absorption, audit trails |
Why asynchronous changes the failure story
A queue converts a hard failure into a delay. If the recommendation service is down, synchronous calls fail and users see an error. With a queue, messages accumulate and drain when it recovers — the user never notices.
This also inverts the dependency direction. Checkout does not know its consumers exist. Adding a fourth consumer requires no change to checkout at all — which is the real reason event-driven architectures scale organizationally, not just technically.
The trade you are actually making
| Concern | Synchronous RPC | Asynchronous messaging |
|---|---|---|
| Latency to answer | Immediate | Eventual — could be ms or minutes |
| Failure visibility | Instant and obvious | Deferred — needs dead-letter queues and monitoring |
| Load spikes | Passed straight through to the callee | Absorbed by the broker |
| Debugging | One stack trace, one trace ID | Distributed tracing required to follow the chain |
| Consistency | Easier to keep strong | Eventual by construction |
| Coupling | Temporal — callee must be up now | Decoupled in time and in knowledge |
A practical decision rule
- The caller needs the result to respond → synchronous. Internal and high volume → gRPC; external or browser-facing → REST.
- Many clients, each needing a different slice of a rich graph → GraphQL at the edge, gRPC behind it.
- The work can happen later, or more than one system cares → events.
- The work is slow, bursty, or retriable → queue it, and return an ID the client can poll.
Key takeaway
Default to synchronous for the read path (a user is waiting) and asynchronous for the write side effects (nobody is). Most well-factored systems are a synchronous shell around an asynchronous interior.
Interview signal by level
| Level | What a strong answer sounds like |
|---|---|
| L4 | Picks a protocol and moves on: "REST between services." |
| L5 | Splits the paths: "synchronous for checkout, but email and analytics go on a queue so they can't take checkout down." |
| Staff+ | Quantifies the coupling: "each synchronous dependency multiplies into my availability, so I'd keep the critical path to two calls and push the rest to events — accepting eventual consistency, and here's the dead-letter and lag monitoring that requires." |
Next: once writes are asynchronous, what is a read allowed to return?