ThrottlingException: Rate exceeded

Short answer

A service refused the request because you exceeded a rate or concurrency limit. The critical question is which side was throttled: Lambda refusing to run your function at all, or a service your handler called refusing that call — they appear in different places and have different fixes.

What it means

Throttling is a healthy response to load, not a fault: a service is protecting itself by refusing work it cannot absorb. The reason it is confusing on Lambda is that two entirely different throttles produce similar-looking messages, and only one of them leaves anything in your function's logs.

The first is a Lambda invocation throttle. The account's concurrency limit or the function's reserved concurrency is reached, and Lambda declines to start another execution. Your function does not run. There is no START, no REPORT, and nothing whatsoever in the log group — the evidence lives in the Throttles metric and in whatever called you. Notably this is not counted in the Errors metric either, since no invocation occurred, which is why a throttled function can look completely healthy on an errors dashboard while dropping traffic.

The second is a downstream throttle: your handler ran, called DynamoDB or KMS or STS, and that service refused. This one does appear in your logs, with a stack trace, inside a normal invocation. The fix is on your side of the call — fewer calls, better caching, or lower concurrency — rather than on Lambda's.

Two multiplication effects cause most downstream throttling. Concurrency multiplies call rate: a function making three API calls per invocation, running at 200 concurrent executions, is making six hundred concurrent calls, and a limit that was ample in testing is reached immediately at scale. And control-plane APIs have much lower limits than data-plane ones — sts:AssumeRole and GetSecretValue are the usual offenders — so calling them once per invocation rather than caching them at module scope reaches a quota long before your actual work does. The retry-then-fail sequence in the logs, and a duration inflated by backoff you never see, are what make that visible.

Depends

CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. A Lambda invocation throttle is counted under the Throttles metric, not Errors — the invocation never happened. A throttle from a downstream service inside your handler counts under Errors only if it escapes. LogStitch reports this invocation's status as throttled rather than errored, which is the distinction the CloudWatch metrics also draw.

What it looks like in CloudWatch

This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.

CloudWatch Logs Before

6 raw lines, in the order CloudWatch delivered them.

2026-08-29T12:11:04.220Z START RequestId: 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64 Version: $LATEST
2026-08-29T12:11:04.224Z 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64 INFO Batch received records=250
2026-08-29T12:11:04.902Z 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64 WARN Retry 3/3 after backoff op=BatchWriteItem
2026-08-29T12:11:05.118Z 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64 ERROR ThrottlingException: Rate exceeded
2026-08-29T12:11:05.402Z END RequestId: 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64
2026-08-29T12:11:05.402Z REPORT RequestId: 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64 Duration: 1182.44 ms Billed Duration: 1183 ms Memory Size: 512 MB Max Memory Used: 118 MB Status: error

LogStitch After

The same lines, grouped into the invocation they belong to.

Throttled7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64dur 1.18sbilled 1.18smem 118/512MBlogs 6
  1. 12:11:04.220PLAT
    START RequestId: 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64 Version: $LATEST
  2. 12:11:04.224INFO
    INFO	Batch received records=250
  3. 12:11:04.902WARN
    WARN	Retry 3/3 after backoff op=BatchWriteItem
  4. 12:11:05.118ERROR
    ERROR	ThrottlingException: Rate exceeded
  5. 12:11:05.402PLAT
    END RequestId: 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64
  6. 12:11:05.402PLAT
    REPORT RequestId: 7d4e2b90-6c31-4f08-9a25-1e8b3d7c0f64	Duration: 1182.44 ms	Billed Duration: 1183 ms	Memory Size: 512 MB	Max Memory Used: 118 MB	Status: error
Throttled
Status
1.18s
Duration
1.18s
Billed
118/512MB
Memory
77%
Headroom
No
Cold start
Throttled
Error type

The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.

How to confirm it from the logs

Where the message appears settles which kind you have. A throttle inside an invocation — between START and REPORT, with an SDK stack trace — is a downstream service refusing your call. A Lambda invocation throttle produces no log lines at all, because the function never ran; it appears only in the Throttles metric and in the caller's own error.

For the downstream kind, look at whether retries are visible. Most AWS SDKs retry throttles automatically with backoff, so a single logged Rate exceeded usually means the SDK had already exhausted its attempts. Also check the duration on the REPORT line — invocations that spend seconds longer than usual are often paying for silent retries you never see.

Causes, most likely first

1

A downstream service's per-second limit was reached

How to confirm

Read the exception name and the stack trace to identify the service. DynamoDB, KMS, STS, SQS, Secrets Manager and CloudWatch Logs all have request-rate limits, and a function running at high concurrency multiplies its own per-invocation call rate by the number of concurrent executions.

2

The account's Lambda concurrency limit was hit

How to confirm

Check the Throttles metric for the function and the account's concurrent-execution quota. This produces no function logs at all, so an absence of evidence in CloudWatch Logs combined with a rise in Throttles is exactly the signature.

3

Reserved concurrency on this function is set too low

How to confirm

Look at whether the function has a reserved concurrency configured. Reserved concurrency is a ceiling as well as a guarantee — setting it to 10 means the eleventh concurrent invocation is throttled even when the account has thousands of executions free.

4

A burst arrived faster than concurrency could scale

How to confirm

Compare the timing of the throttles against traffic. Lambda scales up in increments rather than instantly, so a sharp spike from a queue drain or a scheduled fan-out can be throttled even well below the steady-state limit.

5

Every invocation calls a low-limit control-plane API

How to confirm

Look for sts:AssumeRole, GetFunctionConfiguration, or Secrets Manager GetSecretValue being called on each invocation. Control-plane and secrets APIs have far lower limits than data-plane ones, and calling them per request rather than caching at module scope reaches those limits quickly.

Fixes

Fix 1

Cache expensive and rate-limited calls outside the handler

Anything initialised at module scope is created once per execution environment rather than once per invocation. Moving secret fetches, role assumptions and client construction there often removes the throttling entirely, because it cuts the call rate by the number of invocations each container serves.

jsimport { SecretsManagerClient, GetSecretValueCommand }
  from "@aws-sdk/client-secrets-manager";

const client = new SecretsManagerClient({});

// Fetched once per execution environment, not once per invocation.
let cached;
async function secret() {
  cached ??= await client.send(
    new GetSecretValueCommand({ SecretId: process.env.SECRET_ID })
  );
  return cached.SecretString;
}
Fix 2

Retry with exponential backoff and jitter

Throttles are transient by definition, and the AWS SDKs already retry them — but the default attempt count is low for a bursty workload. Raising it with the adaptive retry mode adds client-side rate limiting as well as backoff.

jsimport { DynamoDBClient } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({
  maxAttempts: 5,
  retryMode: "adaptive"   // backoff plus client-side rate limiting
});
Fix 3

Reduce concurrency rather than raising the downstream limit

When the function itself is the source of the load, capping its concurrency protects the downstream service. This is the standard fix for a Lambda draining a queue faster than the database behind it can absorb.

yamlResources:
  ProcessFunction:
    Type: AWS::Serverless::Function
    Properties:
      # Cap the fan-out so the database is never asked for more than it can take.
      ReservedConcurrentExecutions: 25
Fix 4

Raise the quota when the demand is legitimate

Account concurrency and most per-service rate limits are adjustable. Where the traffic is real and the architecture is sound, request an increase rather than engineering around a default.

bash# Current account concurrency quota.
aws service-quotas get-service-quota \
  --service-code lambda --quota-code L-B99A9384

Also seen as

The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.

TooManyRequestsException: Rate Exceeded.Rate exceededThrottlingExceptionProvisionedConcurrencyConfigNotFoundExceptionlambda rate exceededlambda throttled 429

Errors that show up alongside this one, or that people mistake for it.

References

LogStitch finds this automatically, across every invocation in your account.

Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.