Lambda Cost Optimisation
Lambda often costs almost nothing. Then you hit scale, or someone adds a memory-hungry initialisation routine, or Provisioned Concurrency gets left on, and the bill climbs fast. Understanding the Lambda pricing model deeply is the key to keeping costs predictable.
The Lambda pricing model
Lambda charges for two things:
- Requests — $0.20 per 1 million invocations (after the free tier)
- Duration — $0.0000166667 per GB-second (after the free tier)
Duration is billed in 1-millisecond increments. A GB-second is one second of execution time at 1GB of memory. If your function is allocated 512MB (0.5GB) and runs for 200ms, you consumed 0.5 × 0.2 = 0.1 GB-seconds.
The free tier is generous: 1 million requests/month and 400,000 GB-seconds/month — permanently, not just for new accounts.
At 128MB allocation (the minimum), 400,000 GB-seconds = 3.2 million seconds of execution. That’s a lot of function-runs before any charge applies.
Worked cost example: 10 million invocations per month
A payment processing webhook handler runs 10 million times per month. Each invocation runs for approximately 500ms at 512MB memory.
Request charges: 10,000,000 requests − 1,000,000 free = 9,000,000 billed 9,000,000 ÷ 1,000,000 × $0.20 = $1.80
Duration charges: Memory in GB: 512 ÷ 1024 = 0.5 GB Total GB-seconds: 10,000,000 × 0.500s × 0.5 GB = 2,500,000 GB-seconds Minus free tier: 2,500,000 − 400,000 = 2,100,000 billable GB-seconds 2,100,000 × $0.0000166667 = $35.00
Total Lambda cost: $36.80/month
That’s genuinely inexpensive for 10 million API invocations. The cost only becomes significant when scale is high or functions run for a long time with a lot of memory.
Now consider if the same function is allocated 2048MB (perhaps because someone thought “more memory = faster” without measuring): Total GB-seconds: 10,000,000 × 0.500s × 2 GB = 10,000,000 GB-seconds Billable: 9,600,000 GB-seconds × $0.0000166667 = $160.00
That’s a 4× cost increase with no benefit if the function doesn’t actually use that extra memory.
Memory allocation and the cost-performance trade-off
Lambda is unusual because memory allocation controls not just RAM but also proportional CPU. A 512MB function gets half the CPU of a 1024MB function.
For CPU-bound functions, more memory means faster execution. If a function runs in 1,000ms at 512MB but 400ms at 1024MB, the cost comparison is:
- 512MB: 1.0s × 0.5GB = 0.5 GB-seconds
- 1024MB: 0.4s × 1.0GB = 0.4 GB-seconds
The higher-memory version is actually cheaper per invocation because it finishes faster.
For memory-bound functions (those that actually use RAM for caching, large datasets, etc.), more memory may not speed things up, and you’re paying for memory that doesn’t benefit performance.
The practical approach: don’t guess. Use the AWS Lambda Power Tuning tool to find the optimal memory setting empirically.
Lambda Power Tuning
AWS Lambda Power Tuning is an open-source state machine (deployed via AWS Step Functions) that invokes your function at multiple memory settings, measures execution time and cost, and produces a chart showing the optimal configuration.
It tests your function at memory levels like 128MB, 256MB, 512MB, 1024MB, 1769MB (1 vCPU equivalent), 2048MB, and 3008MB, running each multiple times to get stable measurements.
The output tells you:
- The cheapest memory setting (minimises GB-seconds)
- The fastest memory setting (minimises execution time)
- The balanced setting (best cost-performance ratio)
For most API handler functions, the optimal memory setting is somewhere between 256MB and 1024MB. Functions doing heavy JSON parsing or encryption often perform better at 512–1024MB than at 128MB despite the higher price-per-second.
Reducing duration through code efficiency
Duration is the other cost lever. Cutting execution time in half cuts duration costs in half, regardless of memory setting.
Common duration reduction techniques:
Initialise outside the handler — Lambda reuses execution environments for subsequent invocations. Database connections, SDK clients, and cached data initialised outside the handler function are reused across invocations, not re-created each time. This cuts cold start time and hot invocation time.
# BAD: creates a new DB connection every invocation
def handler(event, context):
conn = create_db_connection()
result = conn.query(...)
# GOOD: creates connection once, reused across warm invocations
conn = create_db_connection()
def handler(event, context):
result = conn.query(...)Avoid unnecessary network calls — Each outbound API call adds latency and duration cost. Cache responses for data that doesn’t change frequently.
Use efficient data formats — Parsing large JSON payloads takes time. If you control both sides of the API, consider more compact formats or pre-parsing at the event source.
Set appropriate timeouts — Lambda functions have a configurable timeout (up to 15 minutes). If a function hangs on a network call, it runs until the timeout, charging for the entire duration. Set timeouts as low as the realistic maximum execution time plus a buffer.
Reducing unnecessary invocations
Invocations cost $0.20 per million — cheap individually, but the architecture can drive up the count unnecessarily.
SQS batch size — if a Lambda function processes SQS messages, the batch size controls how many messages are processed per invocation. A batch size of 1 means one invocation per message. A batch size of 10 means one invocation for 10 messages. For a queue receiving 1 million messages/day, batch size 10 reduces invocations from 1 million to 100,000.
EventBridge vs polling — some event patterns use Lambda to poll a source on a schedule (e.g., check this S3 bucket every minute for new files). S3 event notifications or EventBridge rules trigger Lambda only when something actually happens, eliminating polling invocations when nothing has changed.
Dead letter queues — without a DLQ, failed Lambda invocations from asynchronous sources retry automatically, potentially invoking the function many times for a single failed event. Configuring a DLQ limits retries and prevents unbounded retry cost.
See Lambda overview and Lambda scaling for the architecture patterns behind these decisions.
Provisioned Concurrency: when it makes sense
Cold starts occur when Lambda needs to initialise a new execution environment. For most functions this adds 100–500ms. For Java functions with large class loading, it can add 5–10 seconds.
Provisioned Concurrency keeps execution environments initialised and warm, eliminating cold starts. The cost:
- Provisioned Concurrency: $0.015 per GB-hour of provisioned capacity
- Plus normal invocation and duration charges
For a function at 512MB (0.5GB) with 10 provisioned concurrent executions running 24 hours/day: 10 × 0.5GB × 24hr × $0.015 = $1.80/day = $54/month
Plus normal invocation/duration charges on top.
$54/month is cheap for eliminating cold starts at consistent scale. But if the use case is a low-traffic internal API that gets 1,000 requests/day, cold starts affect a tiny fraction of users — Provisioned Concurrency isn’t worth $54/month.
Use Provisioned Concurrency only when:
- Cold starts cause measurable user-facing latency (not just background jobs)
- You have consistent minimum traffic to justify the always-on cost
- The alternative (larger container-based service) is more expensive
Lambda vs EC2 cost comparison
For API workloads, Lambda’s per-request pricing looks cheap until you calculate the equivalent EC2 cost. At very high scale, EC2 or Fargate with Auto Scaling can be cheaper.
Rough break-even: if your Lambda functions handle more than about 30–40 million requests/month of non-trivial work (say, 500ms duration at 512MB), a small fleet of EC2 instances with Auto Scaling may approach similar cost with more predictability.
Below that level, Lambda almost always wins on pure cost, plus you get automatic scaling and zero infrastructure management.
Note: Lambda’s permanent free tier (1M requests + 400K GB-seconds per month) applies per account, not per function. If you have 50 functions, they all share the same free tier pool. High-volume functions will consume the free tier quickly, after which all functions in the account start incurring charges.
Common mistakes
- Setting maximum memory without measuring — 10GB of Lambda memory is available. Setting a function to 3GB “to be safe” when it uses 200MB costs 24× more than needed per GB-second.
- Leaving Provisioned Concurrency on indefinitely — Provisioned Concurrency is often added to solve a cold start complaint, then forgotten. Audit your Lambda functions monthly for unused provisioned concurrency.
- Processing SQS messages one at a time — A batch size of 1 is the default. For workloads that process messages individually, increase the batch size to reduce invocation count and cost without changing the processing logic.
- Not setting function timeouts appropriately — A Lambda waiting on a hung network call until the 15-minute maximum timeout charges for the entire 15 minutes. Set timeouts close to the realistic maximum and handle errors explicitly.
- Verbose logging in production — Lambda logs go to CloudWatch Logs. A function that logs every request in detail at high volume can incur more in CloudWatch charges than in Lambda charges. Use structured logging and set appropriate log levels.
Summary
- Lambda charges for requests ($0.20/million) and duration (GB-seconds) — most low-traffic functions cost almost nothing
- More memory is not always more expensive — faster execution at higher memory can reduce total GB-seconds
- Lambda Power Tuning finds the optimal memory setting empirically for your specific function
- Initialise connections and clients outside the handler to reuse them across warm invocations
- Increase SQS batch size to reduce invocation count for queue-processing functions
- Provisioned Concurrency solves cold starts but adds ~$54/month per 10 concurrent environments at 512MB — only worth it when cold starts cause real user pain
- The permanent free tier is 1M requests + 400K GB-seconds per account per month
Frequently asked questions
Does allocating more memory to a Lambda function always cost more?
Not necessarily. Lambda charges for duration × memory. If doubling memory cuts execution time by more than half, the total cost goes down. The AWS Lambda Power Tuning tool finds the memory setting that minimises either cost or execution time for your specific function.
What is Provisioned Concurrency and when should I use it?
Provisioned Concurrency keeps Lambda execution environments initialised and ready, eliminating cold starts. It costs roughly $0.015 per GB-hour of provisioned concurrency plus normal invocation charges. Only use it when cold starts cause measurable user pain — for most APIs, the trade-off is not worth it.
What is included in the AWS Lambda free tier?
Lambda includes a permanent free tier: 1 million requests per month and 400,000 GB-seconds of compute per month. For a 128MB function (0.125 GB), that is 3.2 million seconds of execution per month, always free. Most low-traffic applications never leave the free tier.