AWS Lambda Scaling and Concurrency Explained
AWS Lambda scales automatically by running concurrent execution environments, one per simultaneous request. Concurrency is the unit you control, not servers or threads. This guide explains how scaling works, what concurrency controls are available, when to use reserved vs provisioned concurrency, and how to diagnose throttling when things go wrong.
Simple explanation
Lambda does not have servers you manage. When your function is invoked, AWS creates an isolated execution environment, a lightweight container that runs your code. If 50 requests arrive at the same time, Lambda runs 50 environments in parallel. If requests arrive one at a time, Lambda uses one environment and reuses it for the next request.
The number of environments running at the same moment is your concurrency. Every scaling decision in Lambda flows from this number. Account limits, reserved concurrency, provisioned concurrency, and throttling all relate to managing how many environments are running simultaneously.
Analogy
Think of Lambda concurrency like checkout lanes at a supermarket. Each lane handles one customer at a time. If 20 customers arrive at once, you need 20 open lanes. Lambda opens new lanes automatically as needed. Your concurrency limit is the maximum number of lanes the store is allowed to have open at once.
How Lambda scaling works
Concurrency is the real scaling unit
Unlike EC2 or containers, you do not choose instance counts or pod replicas. Lambda’s scaling unit is concurrent executions: the number of function invocations running at the same instant. Lambda adds new execution environments automatically when concurrent demand increases, and freezes or terminates idle environments when demand drops.
Each execution environment handles exactly one request at a time. Environments are not shared between concurrent requests. More concurrency means more environments running in parallel, not a queue of requests waiting behind each other.
This matters for sizing. A function that runs fast can handle high throughput with low concurrency. A slow function needs more concurrency to handle the same throughput.
The basic concurrency formula
You can estimate the concurrency your function needs at steady state:
Concurrency = Requests per second × Average duration (in seconds)
Worked example: Your API function receives 500 requests per second and each invocation takes 200ms on average:
500 × 0.2 = 100 concurrent executions needed
Now imagine traffic doubles to 1,000 requests/second during peak hours. You now need 200 concurrent executions. If your account limit is 1,000 and no other function is consuming concurrency, you have plenty of headroom. But if your critical payment API only has 50 reserved concurrency and a spike hits, it throttles regardless of available account capacity.
Use this formula to size reserved concurrency, spot whether a function might starve others, and decide whether provisioned concurrency makes sense for your traffic pattern.
You can check a function’s current concurrency usage in the Lambda console under Configuration > Concurrency, or run aws lambda get-function-concurrency —function-name my-function. Do this before setting reserved concurrency so you know what the function actually uses at peak.
What happens during traffic spikes
Warm environments vs cold starts
If a warm (previously used) execution environment is available, Lambda routes the new request to it immediately with no startup overhead. The only time cost is your handler’s own execution time.
If no warm environment is available (because all existing ones are busy, or because this is the first invocation), Lambda creates a new one. This is a cold start. It involves downloading your code, starting the runtime, and running any initialisation code outside your handler. Cold starts typically add 100 to 300ms for Python and Node.js functions. Java functions can take significantly longer without SnapStart.
Cold starts, warm starts, and strategies to minimise their impact are covered in the Lambda Overview.
Why latency can jump during sudden scale-out
When a traffic spike hits and Lambda needs to create many new environments quickly, every one of those new environments has a cold start. If your function goes from 5 concurrent executions to 200 in a few seconds, 195 new environments are initialising at the same time. This creates a latency spike at the moment of scale-out: p99 latency can look terrible even though the function itself is running correctly.
Lambda does not scale to maximum concurrency instantly. It can reach a substantial initial burst quickly, then continues adding capacity per minute until the account limit is reached. For most workloads this is fast enough. For functions where sudden spikes from near-zero to high concurrency are common and latency matters, provisioned concurrency is the solution. Pre-warmed environments are always ready with no cold start.
Burst scaling limits vary by region and are updated by AWS. For production systems with hard latency requirements, design around provisioned concurrency rather than relying on burst scaling behaviour alone. Check the AWS Lambda concurrency documentation for current regional values.
Lambda concurrency controls
Account concurrency
Every AWS account has a regional concurrency limit: the maximum total concurrent Lambda executions across all functions in that region. The default is 1,000. This pool is shared across every Lambda function in the region.
If one function is using 950 concurrent executions and a second function suddenly spikes, the second function only has 50 executions left before throttling, even if it has never caused problems before. This shared pool is why concurrency planning matters in accounts with multiple Lambda functions.
You can request a limit increase via the Service Quotas console or AWS Support. See the Service Quotas guide for how that process works.
Reserved concurrency
Reserved concurrency allocates a dedicated slice of the account concurrency pool to a specific function. It has two effects simultaneously:
- Ceiling: The function cannot exceed its reserved limit, even if the account has spare concurrency. This caps a noisy function from consuming the whole pool.
- Guarantee: No other function can consume that reserved slice. The capacity is held exclusively for this function.
Reserved concurrency reduces the unreserved pool available to all other functions. If you set reserved concurrency of 300 on one function, the remaining functions share 700 (assuming a 1,000 limit). Plan this carefully: over-reserving starves your other functions.
Setting reserved concurrency to 0 disables the function entirely. Every invocation is throttled immediately. This is useful for emergency shutoffs.
# Reserve 200 concurrent executions for a specific function
aws lambda put-function-concurrency \
--function-name my-api-function \
--reserved-concurrent-executions 200The unreserved pool (account limit minus all reserved concurrency) is shared by functions with no reserved setting. If you reserve heavily for individual functions, other functions may throttle unexpectedly. Monitor the UnreservedConcurrentExecutions CloudWatch metric to track remaining headroom. See Monitoring Lambda for the key metrics to watch.
Provisioned concurrency
Provisioned concurrency pre-initialises a specific number of execution environments so they are always warm and ready, with no cold start delay for the first N concurrent requests.
Provisioned concurrency must be applied to a published version or alias, not to $LATEST. This forces you to deploy versioned code for production, which is good practice anyway. See Deploying Your First Lambda Function for how Lambda versioning and aliases work.
# Configure provisioned concurrency on a published alias
aws lambda put-provisioned-concurrency-config \
--function-name my-api-function \
--qualifier production \
--provisioned-concurrent-executions 50Provisioned concurrency costs extra. You pay for pre-warmed environments whether they receive traffic or not. For cost-conscious deployments, use Application Auto Scaling to schedule provisioned concurrency to match your traffic pattern: scale up before peak hours, scale down at night, instead of running at full capacity 24/7. See Lambda Cost Optimisation for scheduling strategies and cost breakdowns.
Event source concurrency notes
Some event sources have their own concurrency behaviour that interacts with Lambda’s limits:
- SQS: Lambda scales the number of pollers based on queue depth. You can set
MaximumConcurrencyon the event source mapping to cap how many parallel invocations process messages from that queue. This is useful for protecting downstream systems (databases, third-party APIs) that cannot handle high fan-out. - Kinesis and DynamoDB Streams: Lambda processes one batch per shard concurrently. Adding more shards increases parallelism. You cannot have more concurrent executions from a stream than the number of shards.
- Async sources (S3, SNS, EventBridge): Lambda manages an internal retry queue. If your function is throttled, Lambda retries automatically with backoff. Configure a dead letter queue so events are not silently lost after retries are exhausted. See Event-Driven Compute with Lambda for DLQ setup.
Reserved concurrency vs provisioned concurrency
These two features are commonly confused because their names sound similar. They solve different problems and can be used independently or together.
| Feature | Reserved concurrency | Provisioned concurrency |
|---|---|---|
| What it does | Caps the function’s maximum concurrency and reserves capacity from the account pool | Pre-warms a set number of environments so they are always ready |
| Problem it solves | Noisy-neighbour throttling, protecting downstream systems, emergency shutoffs | Cold start latency for latency-sensitive user-facing functions |
| Effect on cold starts | None (does not affect cold start behaviour) | Eliminates cold starts up to the provisioned count |
| Cost | No extra cost, it is just a configuration setting | Extra cost per provisioned environment per hour, regardless of traffic |
| Applies to | Any function, including $LATEST | Published version or alias only (not $LATEST) |
| When to use it | Always, for production-critical functions; also to cap potentially runaway functions | Only when p99 cold start latency is genuinely hurting users |
You can use both together. Set reserved concurrency of 200 on a payment API (guaranteeing that capacity and preventing runaway usage), then set provisioned concurrency of 50 (ensuring 50 environments are always pre-warmed for instant response at peak). If traffic exceeds 50, new environments are created on demand. Those will have cold starts, but only at the margins.
When to use concurrency controls
| Scenario | Recommended approach |
|---|---|
| Public API with unpredictable traffic spikes | Reserved concurrency to guarantee capacity; provisioned concurrency if cold starts visibly affect users |
| Internal batch job or background worker | Reserved concurrency to cap the job from starving production APIs; no provisioned concurrency needed |
| Latency-sensitive user-facing endpoint (checkout, login, search) | Provisioned concurrency scheduled to match peak hours; reserved concurrency as a safety cap |
| Event-driven pipeline (SQS, Kinesis, DynamoDB Streams) | Event source mapping MaximumConcurrency to protect downstream systems; no provisioned concurrency needed |
| Function that must not overload a downstream database or API | Reserved concurrency with a hard cap; ensure callers handle 429 with retry and backoff |
For decisions between Lambda and other compute options, see Choosing Between Lambda, ECS and EC2. If your workload regularly needs sustained high concurrency, containers or EC2 may be a better fit than provisioned concurrency.
Common mistakes
- Confusing scaling with throughput. A function processing 10,000 requests per hour is not necessarily high-concurrency. If each request takes 1ms, you might only need 3 or 4 concurrent executions. Concurrency is about simultaneity, not volume. Use the formula: concurrency = RPS × average duration.
- Not protecting critical functions with reserved concurrency. Without reserved concurrency, one misbehaving function, or a traffic spike anywhere in your account, can consume the entire concurrency pool and throttle your most important APIs. Always set reserved concurrency on production-critical functions.
- Overusing provisioned concurrency. Provisioned concurrency costs money whether environments receive traffic or not. Most Lambda functions do not need it. Only enable it for user-facing functions where cold start latency is measurably hurting users. Check the
InitDurationmetric in CloudWatch for Lambda before enabling it. - Not handling 429 errors in callers. When Lambda is throttled, synchronous callers receive a 429 TooManyRequestsException. If your API clients, upstream Lambda functions, or services do not handle 429 with retry and exponential backoff, they fail hard instead of recovering gracefully. See Debugging Lambda Failures for retry patterns.
- Adding VPC placement unnecessarily. Lambda functions only need VPC placement to access private resources (RDS, ElastiCache). Adding a VPC configuration adds cold start overhead. Only use it when required. See Serverless VPC Access for when VPC placement is actually needed.
- Ignoring retry behaviour for async invocations. Throttled async invocations (from S3, SNS, EventBridge) are retried automatically by Lambda. If retries exhaust after hours of throttling, events are dropped unless you have a dead letter queue configured. Configure DLQs on async functions and monitor them. See Event-Driven Compute with Lambda.
Troubleshooting throttling
What throttling looks like
Throttling occurs when Lambda cannot accept a new invocation because concurrency is at its limit. The symptoms differ by invocation type:
- Synchronous (API Gateway, ALB, direct invoke): The caller receives a
429 TooManyRequestsExceptionimmediately. API Gateway surfaces this as an HTTP 429. Users may see errors if your client does not retry. - Asynchronous (S3, SNS, EventBridge): Lambda retries automatically for up to 6 hours with exponential backoff. Events are not immediately lost, but they are delayed. After all retries fail, the event is dropped unless you have a dead letter queue configured.
- Event source mappings (SQS, Kinesis, DynamoDB Streams): Lambda pauses consumption from the source. For SQS, messages stay in the queue past their visibility timeout and become visible again for retry. For Kinesis and DynamoDB Streams, Lambda retries the batch until the records expire from the stream.
What to check in CloudWatch
Open CloudWatch and look for these Lambda metrics under the AWS/Lambda namespace:
Throttles: count of throttled invocations. Any non-zero value warrants investigation.ConcurrentExecutions: current concurrency per function. Watch for this consistently hitting your reserved or account limit.UnreservedConcurrentExecutions: the remaining shared pool across all unreserved functions. If this is near zero, unreserved functions will throttle.InitDuration: how long cold starts are taking. Consistently high values suggest provisioned concurrency may help.
# Check the current concurrency settings for a function
aws lambda get-function-concurrency --function-name my-function
# Count throttled invocations in the last hour
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Throttles \
--dimensions Name=FunctionName,Value=my-function \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 300 \
--statistics SumWhat to change first
Work through this in order:
- Is the function hitting its reserved concurrency cap? If so, either raise the reserved limit or investigate why traffic spiked. Understand the cause before raising limits.
- Is the account concurrency pool exhausted? Check
UnreservedConcurrentExecutions. If it is near zero, a different function may be consuming the pool. Use reserved concurrency to isolate critical functions. - Is this a burst spike (sudden jump from near-zero)? If the function handles sudden, unpredictable bursts and latency matters, provisioned concurrency is the right fix.
- Are async callers handling retries? Configure DLQs, check retry counts, and ensure your application can handle delayed or out-of-order processing.
- Should you request a quota increase? If your workload legitimately needs more than the default concurrency limit, request an increase via Service Quotas. See Service Quotas for the process.
If your function is consistently failing on startup rather than throttling under load, that is a different problem entirely. See Lambda Function Failed to Start for startup and initialisation failure diagnostics.
Summary
- Lambda scales by running concurrent execution environments, one per simultaneous invocation. Concurrency = requests per second × average duration in seconds.
- The default account concurrency limit is 1,000 per region, shared across all functions. Reserve capacity for critical functions to avoid noisy-neighbour throttling.
- Reserved concurrency caps a function’s maximum concurrency and guarantees that capacity from the account pool. It does not affect cold starts and costs nothing extra.
- Provisioned concurrency pre-warms environments to eliminate cold starts. Use it only for latency-sensitive, user-facing functions. It costs extra whether environments receive traffic or not.
- Throttled synchronous invocations return 429 immediately. Throttled async invocations retry automatically for up to 6 hours. Configure DLQs to avoid silent event loss.
- Monitor
Throttles,ConcurrentExecutions, andInitDurationin CloudWatch to catch concurrency problems before they affect users.
Frequently asked questions
How does AWS Lambda scale automatically?
Lambda scales by running multiple execution environments in parallel. Each concurrent request gets its own environment. When traffic increases, Lambda creates new environments automatically, up to your account's concurrency limit. You do not provision capacity in advance.
What causes Lambda throttling?
Throttling happens when concurrent executions exceed either the account-level limit (default 1,000 per region), a function's reserved concurrency cap, or burst scaling limits during a sudden traffic spike. Throttled synchronous calls return a 429 error; throttled async invocations are retried automatically.
What is the difference between reserved and provisioned concurrency?
Reserved concurrency sets a ceiling on how many concurrent executions a function can use and guarantees that capacity is not consumed by other functions. Provisioned concurrency pre-warms environments to eliminate cold starts. They solve different problems. Use reserved to protect critical functions and cap noisy ones; use provisioned when cold start latency is genuinely unacceptable for users.
Does provisioned concurrency remove cold starts?
Yes. Provisioned concurrency pre-initialises execution environments so they are always warm and ready to respond without a cold start. However, if traffic exceeds your provisioned count, Lambda creates new environments on demand, and those will have cold starts.
How do I know if I need provisioned concurrency?
Check the InitDuration metric in CloudWatch for your function. If p99 init duration is adding unacceptable latency to user-facing requests, provisioned concurrency is the fix. If cold starts only affect occasional background jobs or batch processing, they are usually not worth the extra cost.