CLAUDE LABJP
SONNET5 — Claude Sonnet 5 is now the default model in Claude Code, with a native 1M-token context and promo pricing through Aug 31MCPTUNNEL — MCP tunnels arrives as a Research Preview, letting you reach MCP servers inside private networksIDP — Admins can now provision MCP connectors via their IdP (starting with Okta); Asana, Figma, Linear and more support managed authSANDBOX — Claude Managed Agents can now run tool execution in your own self-hosted sandbox instead of Anthropic's infrastructureFAST — Fast mode for Opus 4.7 is deprecated and will be removed on July 24; migrate to fast mode for Opus 4.8FIXES — Recent fixes cover public gateway endpoints, a confirm prompt for external git worktrees, and MCP request timeoutsSONNET5 — Claude Sonnet 5 is now the default model in Claude Code, with a native 1M-token context and promo pricing through Aug 31MCPTUNNEL — MCP tunnels arrives as a Research Preview, letting you reach MCP servers inside private networksIDP — Admins can now provision MCP connectors via their IdP (starting with Okta); Asana, Figma, Linear and more support managed authSANDBOX — Claude Managed Agents can now run tool execution in your own self-hosted sandbox instead of Anthropic's infrastructureFAST — Fast mode for Opus 4.7 is deprecated and will be removed on July 24; migrate to fast mode for Opus 4.8FIXES — Recent fixes cover public gateway endpoints, a confirm prompt for external git worktrees, and MCP request timeouts
Articles/API & SDK
API & SDK/2026-07-13Advanced

Coalescing Concurrent Claude API Calls: Single-Flight Against Duplicate Inference and Cache Stampede

A design for collapsing identical prompts that fire at the same instant into a single upstream Claude call, using single-flight (request coalescing). In-process and distributed implementations, jittered retries, and negative caching, with measured results.

claude-api80singleflightcoalescingcache-stampederate-limit7production111cost-optimization28

Premium Article

Top of the hour. The summarization tasks I run overnight across four sites wake up at almost the same instant. This morning I was following that one minute through the request logs, and my hand stopped. The exact same "summarize today's news" prompt had gone out to Claude four separate times, spaced only milliseconds apart.

All four answers were effectively identical. But I was billed for four. This is also the very day Claude Code's weekly-limit boost (+50%) ends and Sonnet 5 has just become the default model, so my rate-limit headroom is thinner than before. "Buying the same answer over and over" is a small waste — until the headroom shrinks, and then it starts to sting.

What I needed was not a new cache. It was a way to fold the requests that fire at the same moment into one. This article records that folding — the design and implementation of single-flight (request coalescing), from in-process to distributed, with measurements attached.

When Does Duplicate Inference Happen? The Anatomy of a Stampede

Even with a result cache in place, the duplication does not disappear. In fact, it shows up precisely because you have a cache.

The key is concurrency. A cache hit only happens after someone has called upstream once and finished writing the result. But when several callers plunge into a cache miss at the same instant — as they do at a nightly peak — every one of them concludes "it's not in the cache, so I'll compute it myself," and they all rush upstream together. That is a cache stampede (thundering herd).

MechanismSolvesDoes not solve
Exact-match cacheReusing a request identical to a past oneDuplicate first-time misses running concurrently
Semantic cacheReusing semantically similar requestsConcurrent misses; the rush right after expiry
Single-flightDuplicate identical requests running concurrentlyReuse across time (you still need a cache)

So single-flight and caching do not compete — they play different roles. A cache handles "reuse across time"; single-flight handles "deduplication within the same instant." Only when you layer both does the waste disappear both at the moment of a miss and after a hit. For the result-reuse side of the design, see "Putting a Semantic Cache for the Claude API into Production." This article concentrates on the folding side.

The duplication I measured in my own setup, running several sites as an indie developer, averaged 4.2 calls per minute during the top-of-the-hour peak — the result of related tasks across four sites each firing the same summarization prompt independently.

In-Process Single-Flight: The Minimal Implementation

If everything lives in one Node/Worker process, the implementation is surprisingly small. The heart of it is "share the in-progress Promise by request key." Exactly one caller hits upstream; the rest await the same Promise.

// singleflight.ts
type Entry<T> = { promise: Promise<T>; controller: AbortController };
 
export class SingleFlight<T> {
  private inflight = new Map<string, Entry<T>>();
 
  // fn receives an AbortSignal; if every waiter cancels, upstream stops too.
  async run(key: string, fn: (signal: AbortSignal) => Promise<T>): Promise<T> {
    const existing = this.inflight.get(key);
    if (existing) return existing.promise; // ride along with the identical in-flight request
 
    const controller = new AbortController();
    const promise = fn(controller.signal).finally(() => {
      // Always delete, success or failure. Leaving it leaks memory and pins a stale result.
      this.inflight.delete(key);
    });
 
    this.inflight.set(key, { promise, controller });
    return promise;
  }
 
  get size() { return this.inflight.size; }
}

On the calling side, building a stable key from the prompt is what matters. Unless you fold in the model name, temperature, max_tokens, and system prompt, you will accidentally coalesce requests that were meant to differ.

import { createHash } from "node:crypto";
import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic(); // ANTHROPIC_API_KEY comes from the environment
const sf = new SingleFlight<string>();
 
function requestKey(model: string, system: string, user: string, opts: Record<string, unknown>) {
  const norm = user.trim().replace(/\s+/g, " "); // minimal normalization of whitespace
  const payload = JSON.stringify({ model, system, user: norm, opts });
  return createHash("sha256").update(payload).digest("hex");
}
 
async function summarize(newsText: string): Promise<string> {
  const model = "claude-sonnet-5";
  const system = "You are a concise summarizer.";
  const key = requestKey(model, system, newsText, { max_tokens: 512 });
 
  return sf.run(key, async (signal) => {
    const res = await client.messages.create(
      { model, max_tokens: 512, system, messages: [{ role: "user", content: newsText }] },
      { signal }, // cancel upstream when every rider drops out
    );
    return res.content.map((b) => (b.type === "text" ? b.text : "")).join("");
  });
}

With that small amount of code, identical requests running concurrently in the same process always converge to one. In my nightly batch, this alone dropped the peak duplication from 4.2 to 1.1 calls per minute. The remaining duplicates came from "another process / another worker." That is what we fold next.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A complete TypeScript single-flight implementation that folds simultaneous identical prompts into one upstream call, with cancellation propagation and a memory-leak guard
A distributed variant for multi-worker and edge runtimes: short-TTL lock plus negative caching plus jittered expiry, so the herd does not re-form the instant a result expires
Measured on real nightly automation: duplicate rate 76% to 3%, input tokens down roughly 60% per month, plus an adoption decision table and rollout checklist
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-05-29
Splitting Claude API prompt cache into 5m and 1h tiers — separate TTLs cut cost and stabilize ops
Anthropic's cache_control supports two TTLs: 5 minutes and 1 hour. Splitting them into a two-tier layout — 1h for static system/tools, 5m for variable few-shot — meaningfully changed both my costs and my on-call life. Here's the design with the numbers I observed.
API & SDK2026-05-10
What metadata.user_id in the Claude API Is Actually For — Designing the Abuse-Detection vs. Privacy Trade-off
The metadata.user_id field in the Messages API exists to sharpen abuse detection, but sending raw email addresses creates a privacy problem. Here is the HMAC-based stable pseudo-ID pattern I use, plus a clear set of rules for when to send it and when not to.
API & SDK2026-05-05
The Real Cost of Claude API Extended Thinking in Production — ROI Data by Task Type
Three months of measured cost, quality, and speed data for Extended Thinking across five task categories. Learn exactly when extended thinking is worth it—and when it's not.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →