2026-07-01
You have a service that fetches user profiles from a slow upstream API. A hot user's profile expires from cache. In the next 50ms, 200 requests all miss the cache and stampede the upstream. You just turned one cache miss into 200 duplicate fetches — burning latency, quota, and money on identical work.
Singleflight (popularized by Go's golang.org/x/sync/singleflight) solves this: when N goroutines ask for the same key at the same time, exactly one does the work, and the other N-1 wait for its result. It's dogpile prevention scoped to a single process — no Redis, no distributed locks, no coordination overhead.
How it works: a map keyed by the request identifier holds an in-flight call object (a promise, essentially). First caller inserts the entry and starts the fetch. Subsequent callers find the existing entry and block on its completion channel. When the fetch returns, everyone gets the same result — success or error — and the entry is deleted.
Concrete example: a GraphQL resolver that loads product details by SKU. Under load, the same SKU appears in dozens of concurrent queries. Wrap the loader:
result, err, shared := group.Do("sku:" + id, func() { return db.LoadProduct(id) })shared boolean tells you whether the result was piggybacked — useful for metrics.The math: if your fetch takes 100ms and you get 500 requests/second for a hot key, without singleflight you do ~50 concurrent fetches at any moment. With singleflight, you do one. Load reduction ≈ concurrent requests per key during the fetch window. Higher fanout, bigger win.
The gotchas:
DoChan with a timeout so slow leaders don't hold everyone hostage."user:123" is wrong if the fetch varies by locale — use "user:123:en-US".Singleflight is one of those 30-line patterns that pays for itself the first time a hot key expires under load. Reach for it whenever your read path has expensive, idempotent, keyable work behind it.
