Slow AWS-backed code and expensive AWS-backed code are usually the same problem wearing two hats. Before tuning anything, it helps to know exactly where the milliseconds and the pennies actually go, so the rest of this section's fixes make sense as levers rather than folklore.
Every SDK call is, underneath the method name, an HTTPS request to a regional AWS endpoint. That request has to establish or reuse a TCP/TLS connection, serialize the input, wait for the service to process it, and deserialize the response. None of that is free, and none of it is instant.
Three costs recur across almost every AWS service:
Connection overhead. A fresh TCP handshake plus TLS negotiation before the first byte of the actual request goes out.
Chattiness. Doing in ten round trips what could be done in one, because the code loops and calls a single-item operation repeatedly.
Per-request pricing. Many services bill by the API call (or by throughput consumed per call), so call count maps directly to a dollar figure on the bill.
# --- Python (boto3) ---# Chatty: one PutItem call per itemimport boto3ddb = boto3.client("dynamodb")for item in items: ddb.put_item(TableName="Events", Item=item)
// --- TypeScript (AWS SDK v3) ---// Chatty: one PutItemCommand per itemimport { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";const ddb = new DynamoDBClient({});for (const item of items) { await ddb.send(new PutItemCommand({ TableName: "Events", Item: item }));}
This loop looks harmless, but it turns N logical writes into N billed requests and N round trips, when a batch write API could do the same work in a fraction of the calls.
A client's underlying HTTP connection pool is only warm once a connection has actually been used. The first call on a cold pool pays the full TCP/TLS handshake cost; subsequent calls on the same client reuse an open connection and skip it. This is exactly why constructing a fresh client per call (or per Lambda invocation) is expensive: it throws away a warm pool and pays the handshake cost every single time. The Connection Pooling & Client Reuse page in this section covers the fix; the pool sizing knobs themselves (max_pool_connections, NodeHttpHandler({ maxSockets })) are already covered in the sdk-mechanics section's timeout and connection configuration page.
Each additional call is not just one more round trip. It is one more chance to be throttled, one more retry-eligible failure point, and one more line item on the bill. A workload that makes 10,000 single-item calls has 10,000 opportunities for a ProvisionedThroughputExceededException or a ThrottlingException, where a workload that batches the same work into 40 calls of 250 items each has 40. Fewer calls also means fewer round trips waiting on network latency, which is often the dominant cost in total wall-clock time, more than any local CPU work between calls.
Several heavily used services price directly or indirectly by request count: SQS bills per message (with batching reducing the number of billed requests, not messages), DynamoDB provisioned or on-demand capacity is consumed per read/write request, and S3 bills LIST and GET/PUT/DELETE requests as distinct, metered operations. A script that lists an S3 prefix with a tiny page size, or polls SQS with short intervals, is not just slow - it is generating billed requests at a rate the workload doesn't need. This section's cost-aware patterns page covers the code habits that push request count up or down.
These three costs compound in serverless environments. A Lambda function that constructs a new client per invocation pays connection setup on every single request, since a cold client means a cold pool regardless of whether the Lambda execution environment itself is warm or cold. Moving client construction to module scope, outside the handler, lets a genuinely warm execution environment reuse the pool across invocations - a distinct win from the classic "cold start" discussion, and one that applies on every warm invocation, not just the first.
At scale, the three costs interact with concurrency too. Uncapped parallel calls can hit connection pool limits (queuing invisibly and looking like slowness) or service-side throttling limits (visible as errors and retries). Bounding concurrency, covered in Concurrency Limits & Backpressure, keeps both the local pool and the remote service inside their comfortable operating range.
The practical takeaway for the rest of this section: almost every fix reduces to one of "reuse what's already warm," "do more per call," or "cap how much you ask for at once." Keep those three in mind and the specific techniques in later pages will read as applications of a single idea rather than a grab bag of unrelated tips.
"SDK calls are slow because the SDK itself is slow." - No, the SDK's own serialization work is negligible; the time is network round trips and connection setup.
"Batching is only a latency optimization." - No, most batch APIs also reduce the number of billed requests, making it a cost optimization too.
"A new client per request is safer or more isolated." - No, it discards the connection pool and pays handshake cost every time, with no isolation benefit the SDK doesn't already provide.
"Throttling is a capacity problem, not a code problem." - Sometimes, but often it is a chattiness or concurrency problem: too many small calls arriving too fast for no real throughput gain.
"Request count doesn't matter if the payloads are small." - Many AWS services price and rate-limit per request regardless of payload size, so small payloads don't make chattiness free.
Connection setup (TCP/TLS handshake) on a cold pool, and network round-trip time multiplied by the number of calls made. Local serialization work is comparatively negligible.
Why does call count affect my AWS bill?
Many services (SQS, DynamoDB, S3, and others) price per request or consume per-request capacity, so making more calls for the same logical work directly increases cost.
Does reusing a client actually help performance?
Yes. A reused client keeps its connection pool warm, so most calls skip the TCP/TLS handshake that a cold client, or a client rebuilt per call, has to pay every time.
Is chattiness only a latency problem?
No. Extra calls also mean extra throttling exposure and, for many services, extra billed requests - it is simultaneously a latency and cost problem.
Where should I look first when SDK code feels slow and expensive?
Check whether clients are being constructed once and reused, whether batch APIs exist for the operation being looped, and whether concurrency is unbounded.