Choosing a Communication Style
In one line: "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.
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 |
Asynchronous is not free
Going async does not delete complexity, it relocates it. You now own duplicate handling (brokers deliver at-least-once — Lesson 4 applies unchanged), out-of-order arrival, poison messages, dead-letter queues, and consumer lag. Teams that adopt events to "simplify" and skip that machinery end up with silent data loss instead of loud errors.
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?