The Batching Pattern: Amortizing Per-Call Overhead Across Many Operations

2026-07-03

Every operation has two cost components: fixed overhead (network round-trip, TCP handshake, query parsing, transaction setup) and marginal cost (the actual work per item). When fixed cost dominates, doing one item at a time is wasteful. Batching collects N operations and pays the fixed cost once.

The classic trap is the N+1 query problem. Fetching 500 orders and then querying each order's customer individually is 501 round trips. If your database is 2ms away, that's ~1 second of pure network latency. Batching the customer lookups into one WHERE id IN (...) query drops it to ~4ms — a 250x speedup without changing a single index.

Real-world example: A payments service was writing audit events to Postgres one row at a time — 8,000 rows/second saturating a connection pool. Wrapping writes in a 50ms tumbling window and using COPY for bulk insert dropped CPU by 70% and let a single connection handle 40,000 rows/second. The 50ms added latency was invisible to users; the throughput unlock was massive.

Rule of thumb — the crossover formula:

Three batching triggers, pick based on latency tolerance:

What batching costs you:

Anti-pattern: batching writes to a system that already batches internally (Kafka producers, most SDKs). You're adding latency for savings the client library already gave you. Read the docs before wrapping.

See it in action: Check out Batch minting and batch transfers — Forge College by Forge College to see this theory applied.
Key Takeaway: Batch when fixed per-call overhead dwarfs marginal per-item cost — use hybrid size-or-time triggers, cap the buffer, and isolate per-item failures.

All newsletters