Lambda’s concurrency limits aren’t just about how many functions can run at once; they’re a surprisingly nuanced dance between immediate availability and predictable performance.

Let’s see it in action. Imagine we have a function, my-image-processor, that takes about 500ms to run and we expect a burst of 100 requests per second.

{
  "FunctionName": "my-image-processor",
  "State": "Active",
  "LastModified": "2023-10-27T10:00:00.000+0000",
  "CodeSize": 1234567,
  "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:my-image-processor",
  "Runtime": "nodejs18.x",
  "Role": "arn:aws:iam::123456789012:role/lambda-execution-role",
  "Handler": "index.handler",
  "Timeout": 30,
  "MemorySize": 128,
  "Environment": {
    "Variables": {}
  },
  "TracingConfig": {
    "Mode": "PassThrough"
  },
  "RevisionId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "PackageType": "Image",
  "Architectures": [
    "x86_64"
  ],
  "EphemeralStorage": {
    "Size": 512
  },
  "SnapStart": {
    "ApplyOn": "None"
  },
  "RuntimeVersionConfig": {
    "RuntimeVersionArn": "arn:aws:lambda:us-east-1:123456789012:runtime/nodejs18.x/versions/18.17.1",
    "ResponseStreamingSupported": false
  },
  "KMSKeyArn": "arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
}

If we send 100 requests instantly, and our function takes 500ms, we’ll need 50 concurrent executions (100 requests/sec * 0.5 sec/request). If our account’s default concurrency limit is, say, 1000, we’re fine. But what if we’re in a situation where we know we’ll hit a spike, and we can’t afford even a single dropped request due to throttling?

This is where reserved concurrency and provisioned concurrency come into play.

Reserved Concurrency is a hard cap on the maximum number of concurrent executions your Lambda function can have. It’s a slice of your account’s overall concurrency limit. If you set reserved concurrency for my-image-processor to 50, it can never exceed 50 concurrent executions, even if your account has 1000 available. This is crucial for preventing a single "noisy neighbor" function from consuming all available concurrency and starving other functions in your account.

To set reserved concurrency:

aws lambda put-function-concurrency --function-name my-image-processor --reserved-concurrent-executions 50

If you don’t set this, the function shares the account’s overall concurrency limit, which defaults to 1000 per region (but can be increased via support ticket).

Provisioned Concurrency, on the other hand, is about guaranteeing that a certain number of execution environments are already initialized and ready to go before your traffic hits. This eliminates the cold start latency for those provisioned instances. If you set provisioned concurrency to 50 for my-image-processor, AWS will maintain 50 warm instances of your function. When a request comes in, it’s immediately routed to one of these warm instances, resulting in near-zero latency.

To set provisioned concurrency:

aws lambda put-provisioned-concurrency-config \
    --function-name my-image-processor \
    --qualifier PROVISIONED_ALIAS_OR_VERSION \
    --provisioned-concurrent-executions 50

Note that PROVISIONED_ALIAS_OR_VERSION must be a specific version number (e.g., 1) or an alias pointing to a specific version. You can’t set provisioned concurrency on $LATEST.

Think of reserved concurrency as a maximum speed limit for your function’s car, and provisioned concurrency as having a fleet of ready-to-go taxis waiting at the station. Reserved concurrency is about control and isolation, ensuring your function doesn’t hog resources. Provisioned concurrency is about performance and predictability, ensuring your function is always ready.

The most surprising thing about provisioned concurrency is how it interacts with reserved concurrency. When you configure provisioned concurrency for a function, that amount of concurrency is automatically reserved for it. You don’t need to set reserved concurrency separately if your goal is solely to ensure a certain number of warm instances are available. However, if you also want to set a higher limit than your provisioned amount (e.g., you provision 50, but want to allow up to 100 concurrent executions in total), you still need to set the reserved concurrency to 100.

The next thing you’ll likely encounter is managing the cost implications of provisioned concurrency, as you pay for the time it’s active, not just when it’s used.

Want structured learning?

Take the full Lambda course →