How I turned a Hugo blog into a public MCP server on Netlify — tools, resources and prompts over a …
Tuning AWS Lambda for Cost: The Memory Myth, Measured Tuning AWS Lambda for Cost: The Memory Myth, Measured

Summary
Most Lambda cost advice is folklore passed between blog posts. “Raise the memory, it gets cheaper.” “Switch to Graviton, save 20%.” Both are repeated constantly, both are wrong as stated, and AWS’s own documentation contains the numbers that show why.
This is the version built from primary sources: what you are actually billed for, what the memory slider really does, where the 20% is real and where it evaporates, and how to find the right setting for your function instead of copying someone else’s.
Step 1: Know what you’re actually paying for
Lambda bills two things, and they behave differently:
Duration, in GB-seconds — memory allocated × time running. In us-east-1 on x86 this is $0.0000166667 per GB-second for the first 6 billion GB-seconds a month, dropping to $0.0000150000 and then $0.0000133334 in higher tiers.
Requests, at $0.0000002 each — $0.20 per million. Flat. No tiers.
So what is a GB-second?
It is the unit that trips people up, and it is simpler than it sounds:
One GB-second = one gigabyte of memory, held for one second.
Picture a rectangle. Memory is the height, time is the width, and you pay for the area. Halve either side and you halve the bill. That’s the entire pricing model.
| Memory setting | Runs for | GB-seconds per invocation | Working |
|---|---|---|---|
| 1,024 MB | 1 s | 1.0 | 1 GB × 1 s |
| 512 MB | 1 s | 0.5 | 0.5 GB × 1 s |
| 512 MB | 2 s | 1.0 | 0.5 GB × 2 s |
| 128 MB | 500 ms | 0.0625 | 0.125 GB × 0.5 s |
At $0.0000166667 each, a dollar buys you roughly 60,000 GB-seconds — which is why single invocations feel free and why the bill still surprises people at volume.
Now the part that actually costs teams money:
You are billed for the memory you allocated, not the memory your code used.
A function set to 1,024 MB that never touches more than 90 MB is billed for the full gigabyte, every millisecond it runs. Lambda does not meter your actual heap. That slider is the price tag — which is exactly why “just bump the memory up, it’s fine” is not free advice.
Put the two halves together and a real bill looks like this — one million invocations of a 512 MB function that runs for 200 ms:
- Duration: 0.5 GB × 0.2 s = 0.1 GB-s each → 100,000 GB-s → $1.67
- Requests: 1,000,000 × $0.0000002 → $0.20
- Total: $1.87
Duration is 89% of that bill. Tuning is worth your time. Change the numbers to a 40 ms function and the ratio flips — and so does the advice.
Two consequences
Two things fall straight out of that split, and most tuning advice ignores both:
- Duration has volume discounts. Requests do not. Growing traffic slowly improves your effective duration rate; it never improves your request rate.
- A function’s bill has a shape. A 40 ms function invoked 100 million times is a request-dominated bill. A 4-second function invoked 100,000 times is duration-dominated. Almost every optimization below only moves the duration half.
Work out which half dominates before you touch anything. It decides whether the rest of this article is worth your afternoon.
Expand your knowledge with AWS Lambda Configuration Explained: What to Care About (and Why)
Take — the first number I ask for is what percentage of this function’s cost is duration? Cost Explorer, grouped by usage type, answers it in about a minute.
If duration is under half the bill, stop. Every lever in this article moves the duration half, so the best you can achieve is a fraction of a fraction. Go find the function where duration is 90% — that’s where the same afternoon actually pays.
Step 2: The memory myth
Here is the claim you have read a hundred times: increasing Lambda memory makes functions cheaper, because they finish faster.
AWS’s own documentation puts it far more carefully — “in many cases, increasing the memory available causes a decrease in the duration, and as a result, the overall cost increase may be negligible or may even decrease.” Note what that sentence actually concedes: the baseline expectation is a cost increase that happens to be small.
And AWS’s own worked example, a prime-number function, shows exactly that:
| Memory | Average duration | Cost per 1,000 invocations |
|---|---|---|
| 128 MB | 11.722 s | $0.024628 |
| 1,024 MB | 1.465 s | $0.024638 |
Read that table twice. Eight times the memory made it 8× faster — and it cost one thousandth of a cent more. Not cheaper. Fractionally more expensive.
Run it back through the rectangle and you can see why. At 128 MB the area is 0.125 GB × 11.722 s = 1.465 GB-seconds. At 1,024 MB it is 1 GB × 1.465 s = 1.465 GB-seconds. Identical. The speedup scaled exactly with the memory, so the rectangle kept the same area — and the bill did not move.
That is the honest framing, and it is far more useful than the myth:
Memory buys you speed at roughly neutral cost. It is a latency dial that happens not to hurt your bill much — not a savings lever.
The mechanism is simple arithmetic. You pay memory × time. Price scales linearly with memory, so doubling memory only reduces cost if duration more than halves. That happens for CPU-starved work at low memory settings. It does not happen for I/O-bound work, where your function is waiting on a database or an API and extra CPU shortens nothing.
So: if your function spends its life waiting on network calls, raising memory raises your bill and changes nothing else.
Deepen your understanding in AWS CLI Automation: From Bash Scripts to Go
Take — I don’t let “increase the memory” be proposed as a cost fix in review any more. It is a latency fix, and it should be justified as one: “this is CPU-bound, here’s the p95 we need, here’s the sweep that shows 1,024 MB gets us there.”
The reason this matters beyond pedantry: framed as a savings lever, people apply it to the I/O-bound functions too — where it is pure loss. Framed as a latency dial, it lands only where it works.
Step 3: The 1,769 MB line
Lambda allocates CPU in proportion to memory — there is no separate CPU setting. The number worth memorising is this one, and it is documented exactly:
At 1,769 MB, a function has the equivalent of one vCPU.
Below 1,769 MB you are running on a fraction of a core, and CPU-bound work genuinely speeds up as you add memory. Above it you are buying additional vCPUs — approximately 6 at the 10,240 MB maximum. “Approximately” is AWS’s own word, and the arithmetic explains it: 10,240 ÷ 1,769 is 5.79, not a round 6.
Which leads to the trap AWS states plainly: “if your code runs sequentially, it will only use one vCPU regardless of how many are available. The remaining vCPUs sit idle while you’re still paying for the full memory allocation.”
If your handler is single-threaded — and most Node.js and Python handlers are — memory above roughly 1,769 MB is close to pure waste. You pay linearly for cores you cannot use.
Explore this further in CI Pipeline Basics: From Shell Scripts to a Go Build Runner
Take — a plain Node.js or Python handler sitting above 1,769 MB is a bug until someone proves otherwise. Not a style preference — a defect, with a dollar figure attached.
It is worth auditing for directly, because it is almost always accidental: somebody hit a timeout once, dragged the slider to 3,008 MB, the symptom went away, and nobody ever walked it back. That function has been paying for idle cores ever since.
Step 4: Measure, don’t guess — Power Tuning
The cost/speed curve differs per function, so the only defensible way to pick a memory setting is to measure. AWS Lambda Power Tuning does exactly that: it deploys a Step Functions state machine that runs your function concurrently at several memory settings and charts the result.
It is open source, written by AWS developer advocate Alex Casalboni, documented by AWS, and distributed through the Serverless Application Repository:
arn:aws:serverlessrepo:us-east-1:451282441545:applications/aws-lambda-power-tuning
Deploy it from SAR — but note that SAR materializes a pinned CloudFormation stack at
whichever semantic version you deploy, and stacks do not update themselves. If you keep it
around between audits, redeploy rather than assuming you are on the latest. Then execute
the state machine
against a function ARN with a list of memory values to try — typically something like
128, 256, 512, 1024, 1536, 3008.
It runs your real function in your own account, making real HTTP and SDK calls, so the measurement reflects production behaviour rather than a synthetic benchmark. You get back a recommended memory setting plus a graph of the cost/speed trade-off, and it is scriptable enough to run from CI on every deploy.
The output is the point. AWS’s own example shows a function whose cheapest setting is 1,024–1,536 MB while its fastest is 3,008 MB. For strongly CPU-bound work the cheapest and fastest settings often land on the same value, and Power Tuning will emit a single “balanced” recommendation when they do. The point is not that an optimum can never exist — it is that the curve belongs to your function, and you have to measure it to find out whether you are choosing or compromising.
Discover related concepts in Unix Power Tools Every DevOps Engineer Should Know
Take — the anti-pattern I see most is a team picking one memory value and standardising it across every function in the account, usually 1,024 MB, usually because it felt safe.
There is no fleet-wide right answer. The curve is a property of the individual function’s workload, and an image resizer and a webhook receiver do not share one. Standardise the process — Power Tuning on significant functions, re-run when the workload changes — never the number.
Step 5: Switch to arm64 — and know what the 20% covers
This one is real, and unusually exact. Computed from current us-east-1 rates, arm64 duration costs precisely 0.8× the x86 rate at every pricing tier:
| x86 (tier 1) | arm64 (tier 1) | |
|---|---|---|
| Duration, per GB-second | $0.0000166667 | $0.0000133334 |
| Requests, per million | $0.20 | $0.20 |
Look at the second row. Requests get no Graviton discount at all. So the headline “20% cheaper” applies to the duration component only — and to provisioned concurrency — never to your total bill.
Which means the saving you actually see depends entirely on the shape from Step 1. A duration-dominated bill approaches the full 20%. A request-dominated one — short functions, huge invocation counts — saves a small fraction of that. Same architecture switch, wildly different outcome.
There is also a detail almost nobody knows: the volume tiers are not at the same place on both architectures. x86 changes tier at 6 billion GB-seconds a month; arm64 at 7.5 billion. That looks arbitrary until you multiply it out — 6B × $0.0000166667 and 7.5B × $0.0000133334 are both almost exactly $100,000. In us-east-1 the tiers line up on dollars spent rather than volume — though treat that as an observation about this Region, not a stated AWS design rule: it does not hold everywhere. In ap-east-1 the two first breaks fall about $270 apart, and in eu-central-2 the arm64 break is actually the cheaper of the two.
Migrating is a one-line change in the console, CLI, or IaC — but only if your code is
portable. Anything with native binaries, container images, or compiled layers has to be
rebuilt for arm64. Pure Python and Node.js usually move without incident; anything with a
compiled dependency needs a real test pass.
Uncover more details in AWS Lambda Configuration Explained: What to Care About (and Why)
Take — arm64 is my default for new functions, and the 20% is not the reason. The reason is that for pure-runtime code there is no trade-off to weigh: same behaviour, same limits, lower rate. Defaults should be set where the decision is free.
Migrating existing functions is a different call. Anything with a compiled dependency needs a genuine test pass, and if that function is request-dominated you may spend a sprint’s attention to save single-digit dollars. Set the default going forward; migrate the back catalogue only where duration dominates.
Step 6: Reserved vs provisioned concurrency
These two get confused constantly, and confusing them costs money or wastes effort in opposite directions.
| Reserved concurrency | Provisioned concurrency | |
|---|---|---|
| What it does | Reserves a slice of account concurrency and caps the function at it | Keeps N environments pre-initialized |
| Cold starts | No effect | This is the lever |
| Cost | Free | Billed per GB-second, whether invoked or not |
| Use it for | Guaranteeing capacity; protecting a downstream database from too many connections | Latency-critical request paths |
The single most common mistake: enabling reserved concurrency hoping to reduce cold starts. It does nothing for them. None of those environments are pre-initialized; invocations still pay Init. It is a quota control, not a warmth control.
Provisioned concurrency is the real cold-start fix, and it bills continuously. At current us-east-1 rates, holding 1 GB of provisioned concurrency for a 30-day month costs about $10.80 on x86, or $8.64 on arm64 — before a single invocation. Multiply by your concurrency target before switching it on.
One genuine consolation: invocations that run inside provisioned capacity are billed at a discounted duration rate — $0.0000097222 per GB-second on x86, roughly 42% below the standard on-demand rate. Provisioned concurrency is expensive to hold and cheap to use, so it pays off best when utilisation is high and steady, not spiky.
Journey deeper into this topic with Handling a Million Users an Hour with AWS Lambda (Think Like a System Designer)
Take — reserved concurrency’s best use isn’t guaranteeing capacity, it’s protecting whatever sits behind the function. A Lambda in front of an RDS instance will happily scale to a thousand concurrent executions and open a thousand connections, and the database falls over long before Lambda does. Reserved concurrency is the cheapest back-pressure you will ever configure — and it costs nothing.
For provisioned concurrency, do the multiplication before the meeting, not after. Hold 10 concurrent 1 GB environments around the clock and you’re at roughly $108/month on x86 before a single invocation. That number changes the conversation.
Step 7: Cold starts in 2026 — SnapStart has moved on
If your cold starts are the problem rather than your steady-state cost, the landscape has changed and a lot of writing about it is stale.
SnapStart is no longer Java-only. It now supports Java 11+, Python 3.12+, and .NET 8+ — Python and .NET reached GA in November 2024. It works by running Init once when you publish a version, snapshotting the initialized microVM, and restoring from that snapshot instead of re-running Init.
Two things to know before reaching for it:
- It is free on Java. It is not free on Python and .NET, which add a snapshot cache charge per published version (minimum three hours) plus a restore charge per restore.
- It cannot be combined with provisioned concurrency on the same version, and does not support EFS or ephemeral storage above 512 MB.
And it changes assumptions your code may be making. Anything unique generated during Init —
IDs, secrets, seeded randomness — is now duplicated across every environment restored from
that snapshot, so it has to move into the handler or an after-restore hook. Lambda does
reseed /dev/random and /dev/urandom on restore, and the AWS SDKs already re-establish
connections; custom connection code does not.
Node.js and Ruby still have no SnapStart. For those, the levers remain package size and memory. AWS is concrete here: importing only the DynamoDB client instead of the whole AWS SDK measured 125 ms faster. And a caveat worth knowing for Go and Rust — putting dependencies in a layer can increase cold start time, because the function has to load additional assemblies during Init.
Worth keeping in proportion, though: AWS states cold starts typically affect under 1% of invocations in production, ranging from under 100 ms to over a second. Before you spend money on provisioned concurrency, confirm cold starts are actually hurting your p99 rather than just being visible in logs.
Enrich your learning with Handling a Million Users an Hour with AWS Lambda (Think Like a System Designer)
What changed in March 2026
One update invalidates part of the model above. Lambda now supports up to 32 GB of memory and 16 vCPUs on Lambda Managed Instances, and — the real change — lets you configure the memory-to-vCPU ratio explicitly at 2:1, 4:1, or 8:1.
At 32 GB that means 16 vCPUs (2:1), 8 vCPUs (4:1), or 4 vCPUs (8:1), chosen according to whether the workload is CPU-bound or memory-bound. For the first time you can buy CPU without buying proportional memory — which is precisely the trap described in Step 3.
AWS frames the previous state as the limitation it was: functions were “limited to 10 GB of memory and approximately 6 vCPUs, with no option to customize the memory-to-vCPU ratio.”
The distinction matters, so be precise about it: this applies to Lambda Managed Instances, not standard on-demand functions. Standard Lambda is still 128 MB–10,240 MB with CPU proportional to memory, and everything above still holds. It is available in all Regions where Managed Instances is GA, and configurable from the console, CLI, CloudFormation, CDK or SAM.
If you have a CPU-bound function that has been buying memory purely to get cores, that workaround finally has a real alternative.
Gain comprehensive insights from AWS Lambda Configuration Explained: What to Care About (and Why)
Take — resist the urge to move everything. Standard on-demand Lambda is still the right answer for the overwhelming majority of functions, and the March 2026 change does not alter a single number in Steps 1 through 6 for them.
The specific workload worth moving is the one you can name: CPU-bound, currently parked at some large memory setting purely to get cores, with most of that memory idle. That function has been paying a real premium and now has an exit. Everything else should stay where it is until it has a reason.
The checklist
- Determine the shape of the bill — duration-dominated or request-dominated. Everything else follows from this.
- Don’t raise memory hoping to save money. You are billed for memory allocated, not used. Raise it to buy speed at roughly neutral cost, and only if the work is CPU-bound.
- Measure with Power Tuning rather than copying a number from a blog. Cheapest and fastest are different settings.
- Watch the 1,769 MB line. Past it, single-threaded code pays for idle cores.
- Move to arm64 — exactly 20% off duration, nothing off requests, and rebuild anything with native dependencies.
- Don’t use reserved concurrency for cold starts. Use provisioned, and price it first.
- Check SnapStart if you’re on Java, Python 3.12+ or .NET 8+ — free on Java, not on the others.
- Trim the deployment package. Import the client you need, not the whole SDK.
The theme running through all of it: Lambda’s cost model is simple enough to reason about from first principles, and almost all the popular advice fails because it skips that step. Work out what you’re billed for, measure your own function, and ignore the folklore.
If you want the settings themselves explained rather than the economics, see AWS Lambda Configuration Explained; for what breaks at scale, Handling a Million Users an Hour with AWS Lambda.
Master this concept through Boto3 + AWS Lambda: A Production Serverless Pipeline
References and Further Reading
- Amazon Web Services. Configuring Lambda function memory.
- Amazon Web Services. AWS Lambda pricing.
- Amazon Web Services. Profiling functions with AWS Lambda Power Tuning.
- Amazon Web Services. Lambda function scaling and concurrency.
- Amazon Web Services. Improving startup performance with Lambda SnapStart.
- Amazon Web Services. New for AWS Lambda: 1ms billing granularity.
- Amazon Web Services. AWS Lambda Managed Instances now supports up to 32 GB of memory and 16 vCPUs.
Have you actually measured your Lambda memory setting with Power Tuning, or is it still whatever the template defaulted to?
Similar Articles
Related Content
More from cloud
A no-code walkthrough of livestreaming from your phone to viewers using Amazon IVS, with every …
How to design an AWS Lambda system for a million users an hour, capacity math, concurrency, cold …
You Might Also Like
A real, end-to-end walkthrough of Amazon S3 Files, mounting an S3 bucket on EC2 with the s3files …
Knowledge Quiz
Test your general knowledge with this quick quiz!
A set of multiple-choice questions to test your knowledge.
Take as much time as you need.
Your score will be shown at the end.
Question 1 of 5
Quiz Complete!
Your score: 0 out of 5
Loading next question...
Contents
- Step 1: Know what you’re actually paying for
- Step 2: The memory myth
- Step 3: The 1,769 MB line
- Step 4: Measure, don’t guess — Power Tuning
- Step 5: Switch to arm64 — and know what the 20% covers
- Step 6: Reserved vs provisioned concurrency
- Step 7: Cold starts in 2026 — SnapStart has moved on
- What changed in March 2026
- The checklist
- References and Further Reading

