Synchronous Systems Are a Lie
Electrons don't block. Photons don't wait. Every physical interaction in the universe that your software ultimately executes on is asynchronous — signal propagation, memory access, disk write, network transmission. None of it pauses. None of it holds a thread open waiting for a return value. The universe runs on message passing, state machines, and propagation delays.
Distributed systems, therefore, are physically asynchronous. Not as an implementation detail. As a structural property of the substrate they run on. When service A calls service B and waits for a response, the waiting is not something the physics requires. It is something the programming model imposes. The underlying interaction — packets transmitted, threads scheduled, operations executed, responses encoded and transmitted back — is a sequence of asynchronous physical events from start to finish. The synchrony is a layer we added on top, and like most abstractions, it eventually charges for what it hides.
This post is about why that layer was added, how an entire industry was built on top of it, why the conditions that justified it no longer exist, and why the default should have changed already.
History
The architects of Sun RPC, working in the early 1980s, understood what they were building. The explicit design goal was to make remote calls feel like local calls — to give programmers the cognitive experience of sequential, local execution even when the computation was distributed across machines.
This was a deliberate tradeoff, not an oversight. Sequential reasoning is genuinely how humans track state. A function call has inputs and outputs. State is local. Execution is linear. Your working memory can hold the entire context. Distributed async systems break every one of these properties: state is partial and spread across components, execution is concurrent and non-deterministic from any single vantage point, and correlating a response to the request that produced it requires explicit machinery that the synchronous call stack provided for free.
The synchronous model is a cognitive prosthetic. It substitutes a familiar sequential mental model for the genuinely harder problem of reasoning about concurrent state machines. The call stack becomes your state machine implicitly. The return value becomes your coordination mechanism. You never have to draw the state diagram — the stack draws it for you.
In 1984, on systems with a handful of services, modest traffic, and hardware that made async I/O genuinely complex to implement, this was a reasonable choice. The cognitive cost reduction was real. The consequences were manageable. The tradeoff made sense given the context.
That context no longer exists. The tradeoff was inherited anyway.
REST
HTTP was designed by Tim Berners-Lee as a protocol for transferring hypertext documents between a browser and a server. Request, response, stateless, synchronous. The model was appropriate for its purpose: a human clicks a link, the browser requests a document, the server returns it. The human waits. The interaction completes.
Then the industry decided to use it for everything else.
REST — Representational State Transfer — gave HTTP a theoretical framework for service-to-service communication. Resources, verbs, status codes. Every language had an HTTP library. Every infrastructure team knew how to route HTTP traffic. The path of least resistance for any new service integration was: expose an HTTP endpoint, call it with a client, parse the response. The first generation of web services (SOAP) had tried to layer a more sophisticated protocol on top of HTTP and produced something that was neither simple nor powerful. REST was the correction: strip it back, lean on what HTTP already provides, and make integration trivially accessible.
The result was the most successful de facto standard in the history of distributed systems — and the most consequential lock-in.
JSON over HTTP became the universal service communication protocol. Not because it was the right model for inter-service communication. Because it was familiar, tooled, and required no coordination overhead to adopt. An entire ecosystem crystallised around it: API gateways that route HTTP traffic, service meshes that manage HTTP connections, load balancers optimised for HTTP semantics, API management platforms built on request-response assumptions, observability tooling that models traces as trees of synchronous calls. OpenAPI and Swagger made the synchronous request-response contract a first-class artifact. The API economy — hundreds of companies selling programmatic access to their services — was built entirely on synchronous HTTP.
Microservices accelerated the problem. When the industry decomposed monoliths into independent services, the default communication model between those services was REST over HTTP. One service needed something from another — call it. The result, in system after system, was a distributed monolith: the deployment boundaries of a microservices architecture with the synchronous coupling of a monolith, and all the failure modes that synchronous coupling produces at distributed scale. Services that could not function if their downstream dependencies were unavailable. Latency that stacked with every hop in the call chain. Thread pools that exhausted under load because every thread was blocked waiting for something downstream.
The irony is that HTTP itself, at the transport level, had begun moving away from synchronous semantics. HTTP/2 introduced multiplexing — multiple requests and responses interleaved on a single connection, not serialised. HTTP/3 moved to QUIC, eliminating head-of-line blocking at the transport layer entirely. The protocol was evolving toward async physical reality while the application-layer model built on top of it remained stubbornly synchronous. The industry optimised the pipe while keeping the programming model that filled it with blocking calls.
The REST ecosystem is not going away. The installed base is too large, the tooling too deeply embedded, the mental model too widely held. But understanding that it is a choice — a specific programming model selected for accessibility and familiarity rather than for fit with the physical reality of distributed systems — is the necessary precondition for understanding why the default should be questioned.
The Hardware Argument
The 1984 context in which synchronous defaults were adopted included hardware constraints that made asynchronous programming genuinely difficult. Those constraints have changed substantially, and the change is directional: every significant hardware and platform advancement of the past two decades has reduced the cost of async and reduced the conditions that justified synchronous defaults.
Single-core CPUs made async I/O expensive to reason about. Concurrent state management on a single core required careful interleaving, and the synchronous model's implicit serialisation was a genuine simplification. Multi-core CPUs, now universal even on the smallest cloud instances, make genuine parallelism cheap and available. The cognitive cost of concurrent state management doesn't disappear, but the hardware argument for avoiding it does.
OS-level async I/O was exotic. The select system call existed, but building production systems around non-blocking I/O required sophisticated understanding of OS primitives and produced code that was hard to reason about. epoll on Linux changed the economics significantly. io_uring, introduced in Linux 5.1, changed them further: a shared ring buffer between kernel and userspace that enables truly async I/O with minimal syscall overhead, supporting tens of thousands of concurrent operations with latency characteristics that make the blocking alternative look archaic by comparison. Async I/O is no longer an expert technique. It is the mainstream primitive.
Language runtimes have caught up. Go's goroutines made writing concurrent async code feel sequential — the runtime schedules thousands of goroutines on a small thread pool, and the programmer reasons about sequential state within a goroutine while the runtime handles the async multiplexing underneath. Kotlin coroutines, Python's asyncio, JavaScript's async/await, and Java's Project Loom virtual threads have brought the same model to their respective ecosystems. The cognitive prosthetic that synchronous RPC provided — sequential reasoning over distributed state — is now available through language primitives that don't require blocking threads. The original justification for synchronous defaults was partly a language and runtime limitation. That limitation has been removed.
In-datacenter network latency has collapsed. Modern datacenter networks operate at latencies measured in microseconds, not milliseconds. RDMA (Remote Direct Memory Access) enables memory reads across machines at latencies comparable to local memory access, with kernel bypass removing OS scheduling from the critical path. The gap between "local" and "remote" that made synchronous waiting a significant cost has compressed dramatically. The physical cost of async coordination — a round trip to signal readiness, a round trip to deliver a response — is now small enough that it is no longer a meaningful argument against async designs.
Event-driven infrastructure is now commodity. In the early days of REST, message brokers were complex, expensive to operate, and required specialised expertise. Kafka, built at LinkedIn and open-sourced in 2011, made high-throughput persistent message streaming operationally accessible. Kinesis, Pub/Sub, EventBridge, and similar managed services have made event-driven infrastructure available without operational burden. The async alternative to synchronous REST calls — publish an event, consume it where needed — now has first-class infrastructure support that was not available when the REST default was established.
The conditions that made synchronous defaults reasonable were: async I/O was hard, language runtimes didn't support it well, in-datacenter latency was non-trivial, and event-driven infrastructure was operationally expensive. All four of these conditions have been materially addressed. The hardware and platform environment has moved decisively toward making async natural. The programming model has not followed.
Exception
None of this means waiting is always wrong. There is a narrow, precisely characterised case where synchronous waiting is not a choice but a genuine physical necessity.
Consider a Kafka producer writing with acks=all. The producer waits until the broker confirms replication across the minimum in-sync replica set. Consider a WhatsApp client sending a message. It waits for the server's receipt acknowledgement — the single tick. In both cases, every underlying operation is physically asynchronous. What the caller is waiting for is not computation. It is a commitment signal: confirmation that a state transition has been made durable and that responsibility for the data has transferred across a boundary.
The producer has a genuine state machine decision it cannot make without the ack: has responsibility for this message transferred to the broker, or does it remain mine? Without the ack, the message must be retained for potential retransmission. With the ack, it can be released. This decision is immediate and binary. It cannot be deferred without losing correctness.
This is commitment boundary waiting, and it is categorically different from synchronous RPC. In synchronous RPC, the caller waits for computation it will eventually need, blocking out of habit rather than necessity. In commitment boundary waiting, the caller has a state machine transition it genuinely cannot make without this specific signal, and deferring the decision means losing the correctness guarantee the wait was designed to provide.
The test is one question: does the caller have a state machine transition it cannot make correctly without this signal, before it does anything else? If yes, the wait is honest. If the caller could proceed — with degraded output, with a different strategy, with the result consumed later when it arrives — the synchrony is a choice whose cost should be understood and owned.
Conclusion
The synchronous model was a rational choice in 1984 for a specific set of conditions: limited hardware, immature async runtimes, high coordination cost, and distributed systems small enough that the coupling consequences were manageable. It became the industry default through Sun RPC and was then entrenched at civilizational scale by REST and the ecosystem that grew around it.
The hardware conditions that supported it have changed. The runtime conditions that required it have changed. The infrastructure conditions that made the async alternative expensive have changed. What has not changed is the default — and a default that no longer reflects the conditions it was chosen for is not a default.
The synchronous call is not inherently wrong. It is a choice with a specific cost, and that cost should be made consciously. Treating it as the natural way to build distributed systems is how systems accumulate the coupling, the failure modes, and the resilience debt that follows from it. The physics was always async. The question is when the programming model catches up.