A single Claude request spends nearly all of its wall-clock time waiting on the network and the model. If you need to process fifty documents, running them one after another wastes that waiting time fifty times over. Every official SDK lets you overlap requests using its language's native concurrency primitive — the pattern is the same everywhere; only the syntax changes.
Python: threads or asyncio
The simplest concurrent Python pattern needs nothing beyond the standard library — the synchronous client from the SDK plus a thread pool:
from concurrent.futures import ThreadPoolExecutor
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY
docs = ["First document...", "Second document...", "Third document..."]
def summarize(text):
return client.messages.create(
model="claude-haiku-4-5", max_tokens=1024,
messages=[{"role": "user", "content": f"Summarize: {text}"}],
)
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(summarize, docs))
Threads work well here precisely because the work is I/O-bound. If your application is already built on asyncio, the Python SDK also ships async support — the repository README and the platform docs cover the async client; pair it with asyncio.gather the same way you would any awaitable, and keep the semaphore-bounded pattern described below. Whichever you choose, keep a single client instance shared across workers rather than constructing one per call: the client is where connection reuse lives, and per-call construction throws that away. The same fan-out shape works unchanged against the third-party platform clients — swap Anthropic() for AnthropicBedrockMantle(aws_region="us-east-1") or AnthropicVertex(project_id="...", region="global") and the concurrency pattern is identical.
TypeScript: promises are already concurrent
The TypeScript SDK's client.messages.create({...}) returns a promise (the README's minimal example is await client.messages.create({ max_tokens: 1024, messages: [...], model: ... })). Concurrency therefore costs one idiom: build an array of those promises without awaiting them individually, then Promise.all (or Promise.allSettled, which doesn't abandon the batch when one request fails) to collect results. There is no separate async client to import — JavaScript's runtime model gives you the overlap for free.
Go: goroutines and contexts
The Go SDK's request method takes a context as its first argument — client.Messages.New(ctx, anthropic.MessageNewParams{...}) — which is the hook that makes Go concurrency composable: launch one goroutine per request, share a cancellable context so a failure or timeout can stop the whole batch, and collect results over a channel or an errgroup. This is standard Go fan-out; the SDK doesn't need anything special because blocking calls plus goroutines is Go's async model.
The part everyone skips: bounding concurrency
Your account has rate limits that vary by usage tier (Start, Build, Scale on the Claude API; Claude Platform on AWS limits are likewise managed by Anthropic, and new organizations start on the Start tier). Unbounded fan-out converts "fifty documents" into a burst of 429 responses, and naive retry loops then amplify the burst. Three rules keep concurrent workloads healthy:
- Cap in-flight requests — a worker-pool size, a semaphore, or a channel buffer. Start small and raise it while watching for 429s.
- Back off on 429 with jitter, and honor server retry guidance rather than hammering. See handling 429 rate-limit errors.
- Fail loud per item — use
allSettled-style collection so one bad document doesn't silently sink the batch, and log which items failed.
Where to go next
Concurrent code multiplies whatever error handling you have, so read error handling patterns across SDK languages and configuring retries next. Language basics live in the Python, TypeScript, and Go quickstarts.