Synchronous event chain anti-pattern
Learn how event-triggered synchronous calls stack latency and amplify failures across a service chain, and how async event handling breaks the coupling.
TL;DR
- A synchronous event chain occurs when an event triggers a service call, which triggers another event, which triggers another call, all synchronously in the same request thread.
- Each hop in the chain adds its latency to the total response time. Each service in the chain is a potential failure point that propagates back to the original caller.
- This pattern often hides in event frameworks: "process order event β call inventory service β call email service β call analytics service." What looks like an event pipeline is a synchronous call chain in disguise.
- Break the chain with async: publish events to a queue, let consumers handle them independently without blocking the original caller.
- Latency in a synchronous chain is additive (sum of all hops). Latency in an async system is determined by the critical path alone (the slowest operation the user must wait for).
The Problem
It's 11:02 a.m. on launch day. Your checkout team deployed a "simple" order event handler last week. When a user places an order, the Order service creates the record, then fires an order.created event. Sounds async, right? Except every listener processes synchronously in the same request thread.
Here's the actual call chain:
The user waited 8.1 seconds for an order confirmation. Their request was blocked while Warehouse called Metrics, which called Analytics. Analytics had a slow database query that day. The user's "Place Order" button spun, they clicked again, and now you have a duplicate order.
I've seen this exact failure in two different companies. The worst part: the Order service team didn't even know their latency depended on the Analytics database. The chain was invisible until someone looked at the distributed trace.
The latency budget was eaten by non-critical downstream calls (analytics, metrics) that had no business being in the synchronous critical path of order creation. Remove Analytics from the chain and the response drops to 190ms.
Here's the breakdown of where the time went:
| Service | Latency | Critical for user? | Should be sync? |
|---|---|---|---|
| Order Service | 50ms | Yes (creates order) | Yes |
| Inventory Service | 80ms | Yes (confirms stock) | Yes |
| Warehouse Service | 30ms | No (internal logistics) | No |
| Metrics Service | 20ms | No (observability) | No |
| Analytics Service | 3,200ms | No (dashboard) | No |
| Total (sync chain) | 8,100ms | ||
| Total (critical only) | 130ms |
The difference between 8.1 seconds and 130ms is the cost of the synchronous event chain.
Why It Happens
Teams build synchronous event chains because each decision makes sense in isolation.
"Events are async by nature." Not necessarily. Many event frameworks (Spring ApplicationEvent, Node.js EventEmitter, .NET MediatR) dispatch events synchronously by default. The word "event" tricks you into thinking it's non-blocking, but the handler runs in the calling thread, blocking the original request until it returns.
"We just need one more listener." The chain grows incrementally. First it's Order + Inventory (reasonable, 2 services). Then someone adds email notification. Then analytics. Then fraud scoring. No single addition looks dangerous, but by month six, your checkout latency depends on five downstream services.
"All our services are fast." They are, until one isn't. Latency in a synchronous chain is additive: if five services each take 50ms on a good day, that's 250ms. When one service hits a slow query or a connection pool timeout, the whole chain stalls. I've seen a 3-second Analytics hiccup turn into an 8-second checkout timeout.
"We need the result before we can respond." Sometimes true (inventory check), but teams apply this reasoning to every operation. Ask: "Does the user need this result to see their confirmation page?" If the answer is no, it doesn't belong in the synchronous path.
Real-world examples
This anti-pattern appears wherever "event-driven" meets "in-process dispatch":
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.