N+1 Network Calls
N+1 network calls fetch a list then make one remote call per item, fanning a single operation into N+1 dependency calls that multiply latency and overload downstreams. Use batch endpoints, request coalescing, or aggregation read models to collapse the fan-out.
The N+1 network-calls anti-pattern is the distributed-systems form of the classic N+1 query problem. You make one call to fetch a list of N items, then loop over the list making one more remote call per item to fetch related data — N+1 calls total for what should be one or two. Unlike a database N+1, these calls cross service or network boundaries, so each carries full RPC and latency overhead.
Why It Happens
Microservice and API decomposition pushes related data behind separate services: an order service returns order IDs, a user service resolves names, a pricing service resolves prices. The straightforward code fetches the list, then enriches each element in a loop. It is correct and easy to read, and on a small list against fast local services it is fine. The cost grows silently with result-set size and network distance.
Why It Hurts
Latency grows linearly with N: a 50-item list becomes 51 sequential round trips. The downstream service receives a burst of near-identical small requests, multiplying its load and exhausting its rate limits and connection pools. When the list is large or traffic is high, the fan-out can overwhelm the dependency and trigger cascading failures and retry storms. Tail latency becomes unpredictable because it depends on the slowest of many calls.
Warning Signs
- A loop over a collection that calls a service or API for each element.
- Distributed traces showing one parent span with N nearly identical child spans.
- Endpoint latency that scales with the number of returned rows.
- A downstream service whose traffic is dominated by single-ID lookups.
Better Alternatives
- Batch / multi-get endpoints: pass all IDs in one request (
POST /users:batchGet) and resolve them server-side. - Request coalescing (DataLoader): buffer per-item lookups and dispatch a single batched call per tick, with de-duplication.
- Backend-for-frontend / aggregation service: assemble the composite response once, close to the data.
- Denormalization / read models: pre-join the data the view needs so no per-item call is required.
- GraphQL with batched resolvers to fetch the full graph in one round trip.
How to Refactor Out of It
Find the enrichment loop and the dependency it calls per item. Add or use a batch endpoint on that dependency and replace the loop with a single multi-ID call, mapping results back by key. If you cannot change the dependency, place a coalescing layer in front of it that batches and de-duplicates concurrent lookups. For hot read paths, consider a precomputed read model or BFF aggregation so the composite is built once. Verify with traces that N+1 child spans collapse into one batched span and that latency no longer scales with result size.