Most people can recite the headline of Lambda pricing — you pay for what you use, per millisecond — and stop there. That summary hides the one lever that matters most, which is that the memory slider is secretly a CPU slider, and that turning it up sometimes turns your bill down. If you have never watched a function get both faster and cheaper at the same time, this is the article that explains why, with real numbers.
The billing model, precisely
Lambda charges you on two axes. The first is requests: a flat $0.20 per million invocations. For most workloads this is rounding error. The second is compute, measured in GB-seconds: the memory you allocate multiplied by the wall-clock duration your function runs, billed per millisecond. In us-east-1, on x86, that rate is roughly $0.0000166667 per GB-second. Prices vary by region, so treat every number here as a us-east-1 reference point, not gospel.
The crucial detail is what "memory you allocate" buys you. You set memory anywhere from 128 MB to 10,240 MB in 1 MB steps, and vCPU is allocated proportionally to that setting. At 1,769 MB a function gets the equivalent of one full vCPU. At 3,538 MB it gets two. There is no separate CPU dial — memory is the dial. A 512 MB function has a small fraction of a core and will be CPU-starved on anything compute-heavy, even if it never touches its memory ceiling.
Why more memory can cost less
Here is the mechanism that surprises people. Cost is memory × duration. When you raise memory on a CPU-bound function, you raise the numerator, but you also give it more CPU, which shrinks duration. If duration falls faster than memory rises, total GB-seconds — and therefore cost — go down.
Consider a JSON-transforming, image-resizing, or crypto-signing function that is genuinely compute-bound. Suppose at 512 MB it takes 1,400 ms:
- GB-seconds per invocation:
0.5 GB × 1.4 s = 0.70 - Per million invokes:
0.70 × 1,000,000 × $0.0000166667 ≈ $11.67, plus $0.20 request charge ≈ $11.87
Now bump it to 1,024 MB. With roughly double the CPU, and with the function no longer thrashing garbage collection under a starved heap, it finishes in 620 ms:
- GB-seconds per invocation:
1.0 GB × 0.62 s = 0.62 - Per million invokes:
0.62 × 1,000,000 × $0.0000166667 ≈ $10.33, plus $0.20 ≈ $10.53
The 1,024 MB configuration is cheaper and runs in less than half the time. The 512 MB setting felt frugal and was quietly the more expensive, slower choice. This is not a universal law — an I/O-bound function that spends its time waiting on a database sees no CPU benefit and just gets more expensive at higher memory. That is exactly why you measure instead of guess.
Finding the sweet spot with Power Tuning
The tool for this is AWS Lambda Power Tuning, an open-source Step Functions state machine you deploy from the Serverless Application Repository. You point it at a function and a payload, give it a list of memory values to sweep (say 128, 256, 512, 1024, 1536, 3008), and it runs the function at each setting in parallel, records duration and cost, and returns a visualization plotting cost against speed. You tell it your optimization strategy — cost, speed, or balanced — and it names the winner.
The output almost always has the shape of a U for CPU-bound work: cost falls as you climb out of the starved region, bottoms out somewhere in the middle, then rises again once you are paying for CPU the function cannot use. You want the bottom of that U. Run it against a representative payload, because the optimum for a 2 KB event and a 2 MB event are not the same.
Cold starts and what they cost
A cold start is the init phase: Lambda provisions an execution environment, downloads your code, starts the runtime, and runs any initialization outside your handler — imports, SDK clients, database pools. AWS has moved toward billing init time on cold invocations for functions that are not using provisioned concurrency, so lean initialization is now a cost concern as well as a latency one. Keep heavy work lazy, trim your dependency tree, and instantiate only the clients you actually use.
Three mechanisms help:
- VPC-attached functions used to be the horror story — attaching an ENI per scaling event added many seconds. The Hyperplane ENI architecture fixed that years ago by sharing network interfaces, so a VPC Lambda today has cold starts comparable to a non-VPC one. If your mental model still says "never put Lambda in a VPC because of cold starts," update it.
- SnapStart takes a Firecracker microVM snapshot of the fully initialized environment and restores from it, cutting cold starts dramatically. It started as Java-only and expanded to other managed runtimes. For Java there is no additional charge; for Python and .NET, AWS bills for snapshot caching (per GB-hour) and for each restore (per GB), so it is close to free but not literally free — check the current terms for your runtime.
- Provisioned concurrency keeps a pool of environments initialized and warm. You pay for that pool by GB-second whether or not it is invoked, at a lower per-GB-second rate, plus a discounted duration charge on actual invocations. The economics are a utilization question: if the warm pool is busy most of the time, provisioned concurrency can be cheaper than on-demand at that latency; if it sits idle, you are paying rent on empty rooms. Reserve it for latency-sensitive, predictable traffic — a login endpoint at business hours — not for bursty background jobs.
arm64 is the easiest 20% you will ever save
Lambda runs on Graviton (arm64) as well as x86, and the arm64 GB-second rate is roughly 20% lower — about $0.0000133334 in us-east-1 — often with equal or better performance. The only cost is compatibility: your code and every native dependency must have an arm64 build. For interpreted runtimes and most pure-language packages this is a config change and a redeploy. Combine arm64 with a power-tuned memory setting and you have compounded two independent discounts on the same function.
Function URLs versus API Gateway
How you front a Lambda matters to the bill. A Function URL is a built-in HTTPS endpoint with no additional per-request charge — you pay only for Lambda. API Gateway HTTP APIs cost about $1.00 per million requests; REST APIs about $3.50 per million, plus data. At ten million requests a month that is a $10, $10, or $35 line item respectively — trivial until it is not, and at a billion requests the gateway can dwarf the compute.
The catch is that the gateway is not just a tollbooth; it is request validation, throttling, usage plans, custom authorizers, WAF integration, and mapping templates. If you need those, pay for them. If you have a simple internal webhook or a service-to-service endpoint that just needs to run a function, a Function URL removes a whole billed hop. An Application Load Balancer is a third option, priced by LCU, that makes sense when Lambda sits alongside container targets behind the same listener.
The short version
Set memory with a power-tuning curve, not a hunch — the frugal-looking small setting is frequently the expensive one. Move to arm64 unless a native dependency stops you. Keep init lean now that cold starts can be billed, and reach for SnapStart or provisioned concurrency only when latency data says you need them. Then look at what fronts the function, because the cheapest request is the one that never had to pay a gateway to arrive.