Lambda’s memory setting is really a compute dial: CPU is allocated in proportion to memory, from a fraction of a vCPU at 128 MB up to 6 vCPUs at 10,240 MB, with roughly 1,769 MB corresponding to one full vCPU. Because you are billed in GB-milliseconds — memory times duration — doubling memory that halves duration costs the same and finishes twice as fast, and often the duration drop is big enough that more memory is actually cheaper. The sweet spot is found by measurement, not intuition, and the measurement takes about an hour.
What’s happening
You configure exactly one performance knob on a Lambda function: memory, from 128 MB to 10,240 MB. CPU, and to a degree network throughput, scale with it linearly. There is no separate CPU setting.
The billing math is simple: cost per invocation = request charge + (memory in GB) x (billed duration in ms) x (per-GB-ms rate). This means cost is a product of two factors you influence in opposite directions. Give a CPU-bound function more memory and duration falls; whether cost rises or falls depends on which effect wins. For a function doing JSON parsing, image resizing, compression, or cryptography, the duration curve typically drops steeply between 128 MB and about 1,769 MB (where the function first has a full vCPU), then flattens. Past the flat point you are paying more per millisecond for milliseconds that no longer shrink.
Functions that spend their life waiting — on an HTTP response, a database query, a downstream service — behave completely differently. Waiting costs the same at any memory size and gets no faster with more CPU, so I/O-bound functions belong at low memory settings, with just enough headroom that the runtime and your data structures fit.
Multi-threaded and multi-process runtimes complicate one edge: below one full vCPU, background threads (metrics flushers, connection keep-alives) steal cycles from your main work; above ~1,769 MB, extra vCPUs only help if the code actually uses more than one core, which most handler code does not.
What to check first
- Max Memory Used vs Memory Size. The REPORT line prints both. A function configured at 1,024 MB that never exceeds 190 MB is not necessarily oversized — the extra may be buying useful CPU — but it tells you memory pressure is not the constraint.
- Whether the function is CPU-bound or I/O-bound. If duration barely moves when you change memory, it is I/O-bound and the smallest workable size wins.
- Whether anyone is waiting. For async pipelines, shaving 400 ms is worth nothing unless it changes cost. For synchronous APIs it is user-facing latency.
- Out-of-memory kills.
Runtime exited with error: signal: killedor errors right as Max Memory Used approaches Memory Size mean you are undersized, full stop.
How to verify
Pull actual usage from the logs. In CloudWatch Logs Insights:
filter @type = "REPORT"
| stats count() as invocations,
avg(@duration) as avgMs,
pct(@duration, 95) as p95Ms,
max(@maxMemoryUsed / 1024 / 1024) as maxMemUsedMB,
max(@memorySize / 1024 / 1024) as memConfiguredMB
Check the current setting:
aws lambda get-function-configuration \
--function-name my-function \
--query '{Memory: MemorySize, Timeout: Timeout, Arch: Architectures}'
For the actual sweet spot, run AWS Lambda Power Tuning — the open-source Step Functions state machine that invokes your function at a range of memory sizes with real payloads and graphs cost against duration. It turns the guessing into a picture. AWS Compute Optimizer also produces Lambda memory recommendations from your production history once it has enough data; we covered its rightsizing behaviour in Compute Optimizer rightsizing. The billing dimensions themselves are documented at https://docs.aws.amazon.com/lambda/.
The approach
- Classify the function first. Ten minutes reading the code tells you if the time goes to compute or to waiting. I/O-bound: set memory to roughly 1.5-2x observed Max Memory Used and stop. CPU-bound: continue.
- Power-tune with production-shaped payloads. A tuning run with a trivial test event lies to you — a 100-byte payload parses fast at any memory size. Use captured real events, and tune for the p95 payload, not the average.
- Read the cost-duration curve, not just the cheapest point. Power Tuning shows both. Frequently there is a setting that costs 5% more than the minimum and cuts duration 40% — for a synchronous API that trade is usually right.
- Mind the 1,769 MB line for single-threaded code. Beyond one full vCPU, single-threaded handlers gain little. If the curve keeps improving past it, your runtime is using the second core (JIT, GC, crypto offload) — fine, but verify rather than assume.
- Apply, then re-measure in production. Set the new value with
aws lambda update-function-configuration --function-name my-function --memory-size 1024and compare a week of REPORT stats to the prior week. Synthetic tuning and production traffic occasionally disagree.
When this is the wrong move
Memory tuning is a second-order optimization. If a function’s duration is dominated by an unindexed query, an N+1 call pattern, or a missing cache, fix that first — no memory setting rescues bad I/O. And if a function runs a few thousand times a month, the total spend is pocket change at any setting; spend the hour somewhere with more use. Tuning matters when invocation volume is high, duration is material, or a synchronous user is waiting.
Common mistakes
- Setting every function to 128 MB “to save money” — for CPU-bound work this is frequently the slowest and most expensive setting at once, because duration balloons faster than the memory rate shrinks.
- Tuning once at launch and never again. Dependencies grow, payloads grow, and last year’s sweet spot drifts.
- Ignoring that billed duration is what you pay, and that since 2025 the init phase of managed runtimes is billed on on-demand invocations too — a heavy init at high memory is a real line item.
- Copying one function’s memory setting to a whole service. Handler workloads differ; tune per function, at least for the top spenders.
Keeping it under control
- Add the Logs Insights memory/duration query to a monthly review of your top ten functions by spend.
- Alarm on Max Memory Used exceeding ~90% of Memory Size to catch creeping payloads before OOM kills start.
- Re-run Power Tuning after any dependency upgrade that touches serialization, crypto, or compression.
- Record the chosen memory size and the tuning date in the IaC comment so the next engineer knows it was deliberate, not folklore.
The memory dial is the one lever Lambda gives you over both speed and cost, and it rewards an hour of honest measurement far more than any amount of forum folklore. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.