/user/kayd @ devops :~$ cat lambda-cost-tuning-memory-graviton.md

Tuning AWS Lambda for Cost: The Memory Myth, Measured Tuning AWS Lambda for Cost: The Memory Myth, Measured

QR Code linking to: Tuning AWS Lambda for Cost: The Memory Myth, Measured
Karandeep Singh
Karandeep Singh
• 17 minutes

Summary

Most Lambda cost advice repeats folklore. This walks through what AWS actually documents and bills — GB-seconds, the 1,769 MB one-vCPU line, the exact 20% arm64 discount and where it does not apply — then shows how to measure your own function instead of guessing.

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.

note
Prices in this article are the us-east-1 rates from AWS’s public Price List API as of 10 August 2026. Lambda pricing is per-Region and does change — check the pricing page before you budget against these. The mechanics matter more than the exact digits.

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 settingRuns forGB-seconds per invocationWorking
1,024 MB1 s1.01 GB × 1 s
512 MB1 s0.50.5 GB × 1 s
512 MB2 s1.00.5 GB × 2 s
128 MB500 ms0.06250.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:

warning

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.

take

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.

tip
Duration is billed rounded up to the nearest 1 ms. That has only been true since December 2020 — before that Lambda rounded to 100 ms. It is why tuning advice written before 2021 doesn’t transfer: shaving a function from 90 ms to 30 ms used to save you exactly nothing, because both rounded to 100 ms. Today it saves two-thirds.

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:

MemoryAverage durationCost per 1,000 invocations
128 MB11.722 s$0.024628
1,024 MB1.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.

take

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.

take

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.

warning
You will see “3,538 MB = 2 vCPUs” repeated in blog posts. AWS does not publish that number; it is just 1,769 × 2. The documented facts are the 1,769 MB one-vCPU point and the 6 vCPUs available at 10,240 MB. Treat anything in between as an inference, not a spec.

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.

take

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.

tip
Don’t want to deploy anything? AWS Compute Optimizer produces Lambda memory recommendations natively. Less precise than a real measurement sweep, but it is free, managed, and a reasonable first pass across a whole account.

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.

take

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 concurrencyProvisioned concurrency
What it doesReserves a slice of account concurrency and caps the function at itKeeps N environments pre-initialized
Cold startsNo effectThis is the lever
CostFreeBilled per GB-second, whether invoked or not
Use it forGuaranteeing capacity; protecting a downstream database from too many connectionsLatency-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.

take

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.

tip
Reserved concurrency has one more trick: set it to 0 and the function stops processing events entirely. It is the cleanest kill switch for a runaway function, and provisioned concurrency has no equivalent.

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.

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.

take

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

  1. Determine the shape of the bill — duration-dominated or request-dominated. Everything else follows from this.
  2. 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.
  3. Measure with Power Tuning rather than copying a number from a blog. Cheapest and fastest are different settings.
  4. Watch the 1,769 MB line. Past it, single-threaded code pays for idle cores.
  5. Move to arm64 — exactly 20% off duration, nothing off requests, and rebuild anything with native dependencies.
  6. Don’t use reserved concurrency for cold starts. Use provisioned, and price it first.
  7. Check SnapStart if you’re on Java, Python 3.12+ or .NET 8+ — free on Java, not on the others.
  8. 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.

References and Further Reading

Question

Have you actually measured your Lambda memory setting with Power Tuning, or is it still whatever the template defaulted to?

Similar Articles

More from cloud

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.