Daniel Broadhurst

ArticlesAWSServerless

Lambda Memory Size for Node.js: What 160 Benchmarks Say

27 Oct 2026 · 6 min read

Ink sketch of a large dial marked with Lambda memory sizes from 128 MB to 3,008 MB, its pink needle resting at 512 MB, beside a stopwatch and a small stack of coins

More Lambda memory makes your function faster, cheaper, both or neither, depending on which blog you read. Some benchmarks say memory does nothing for cold starts. Others say switching to Arm cuts them by double digits. Both can be true, because they’re measuring different things.

The short answer from my own data: for CPU-bound Node.js work, 512 MB was the cheapest setting and 1,024 MB the fastest one worth paying for. For I/O-bound work, memory bought nothing but a bigger bill. On cold starts specifically, my benchmarks can’t tell you much, and I’d rather explain why than pretend otherwise.

Three questions hiding inside “how much memory?”

“Does memory make Lambda faster?” is really three questions:

  • Cold start: how long a new instance takes to download your code, start the runtime and run your module-level code (the INIT phase).
  • Warm duration: how long the handler runs on an instance that’s already up.
  • Cost: memory times billed duration, plus a fee per request.

Lambda gives a function CPU in proportion to its memory, reaching the equivalent of one vCPU at 1,769 MB (AWS docs). So memory obviously matters for anything limited by CPU. The argument is over which of those three things is.

Most of the disagreement online comes from mixing these three up.

What my benchmarks measured, and what they didn’t

For my MSc I ran 160 benchmark runs on Node.js Lambda functions, mostly comparing four ways to chain functions together. Part of that work isolated memory: a single CPU-bound function (a Fibonacci busy loop) at 10 concurrent executions, across six memory sizes from 128 MB to 3,008 MB. A matching I/O-bound function waited on a simulated external API.

Each run summary recorded mean end-to-end latency, billed duration, the number of cold starts and cost per million requests.

What the summaries don’t have is init duration broken down by memory size. Cold starts were counted, not timed per tier, and the latency means mix cold and warm invocations together. Architecture wasn’t one of my variables either.

So my numbers describe what a caller waits for and what it costs, not how long INIT takes. For most functions that’s the more useful pair anyway: AWS says cold starts typically happen in under 1% of production invocations (AWS Compute Blog).

Warm duration: where memory clearly earns its keep

Here’s the CPU-bound function across all six memory sizes:

Memory Mean latency Cost per million requests
128 MB 738 ms $1.90
256 MB 556 ms $1.97
512 MB 415 ms $1.76
1,024 MB 243 ms $1.86
1,792 MB 242 ms $2.57
3,008 MB 219 ms $3.03

A few things stand out:

  • 128 MB to 512 MB: 44% faster and 7% cheaper. The price per millisecond quadrupled, but the milliseconds fell further.
  • 256 MB was the dearest setting below 1,792 MB. It was 25% faster than 128 MB, which wasn’t enough to pay for doubling the memory. The cost curve has a dip in it, not a steady slope.
  • 512 MB to 1,024 MB: 41% faster for 6% more money.
  • Past 1,024 MB: going to 1,792 MB bought 1 ms for 38% more. Going on to 3,008 MB bought 23 ms for another 18%.

The I/O-bound function was the opposite. Latency sat at about 1.29 seconds at every memory size, because it spent its time waiting on the network. At 3,008 MB it cost around 19 times as much as at 128 MB, for exactly the same speed.

Memory buys speed only when the CPU is the bottleneck.

One thing I can’t fully explain: the curve flattened at 1,024 MB, not at 1,769 MB where a single-threaded Node.js handler gets a whole vCPU. My best guess, and it is a guess, is that by 1,024 MB the Fibonacci work was mostly done. The remaining 240 ms or so was overhead outside the handler (invocation, network, the odd cold start in the mean), which no memory setting touches.

Why 512 MB came out as the sweet spot

512 MB had the lowest cost per million requests of all six settings, and it was 44% faster than the 128 MB default. That’s why it’s the CPU-bound recommendation in my serverless orchestration decision tool.

It’s worth being plain about what “sweet spot” means here. It’s the cheapest point, not the fastest. If you care about latency, 1,024 MB is a very reasonable trade: 41% faster for 6% more. Beyond 1,024 MB, single-threaded work got almost nothing for the extra money.

512 MB was the cheapest place to stand, not the fastest.

And it was the sweet spot for one workload: a short, CPU-bound loop on Node.js. A function that parses big JSON payloads, resizes images or loads a heavy SDK may land somewhere else. The shape of the curve will probably look similar. Where the dip falls is yours to measure.

Cold starts: where the published numbers disagree, and why

Three benchmarks come up when you search this, and they look like they contradict each other:

  • Momento found Node.js cold starts “did not change in any meaningful way” between 256, 512 and 1,024 MB. After they cut their bundle to 590 KB with esbuild, they saw about 600 ms at 256 MB, 550 ms at 512 MB and 500 ms at 768 MB, and settled on 512 to 768 MB (Momento).
  • K-I-Soft tested 512, 1,024 and 1,769 MB on arm64 and found init duration didn’t depend on memory for three of the four runtimes they tried (K-I-Soft).
  • Chris Ebert found arm64 initialised Node.js faster than x86: 134 ms against 155 ms on Node.js 20 (13%) and 129 ms against 150 ms on Node.js 22 (14%) (Ebert’s write-up, raw results).

Read together, they don’t disagree much. Memory and architecture are different levers. Architecture changes which processor runs your init code; memory changes how much of it you get. For a small Node.js bundle, the published data says init barely moves with memory, moves a little with architecture, and moves most with how much code you load.

Most headlines about memory and cold starts are really about bundle size.

There’s a newer twist. Since 1 August 2025, AWS bills the INIT phase for ZIP-packaged functions on managed runtimes, at the same rate as the rest of the invocation (AWS Compute Blog). That rate depends on memory. So if init doesn’t get faster with more memory, a cold start at 3,008 MB costs about 23 times one at 128 MB for the same work.

If memory doesn’t shorten your cold start, it just makes each one dearer. For a busy function that’s a rounding error. For a spiky, low-traffic one that cold starts often, it isn’t.

If you’re building your own

Here’s the split I’d start from in an AWS SAM template, before measuring anything:

Transform: AWS::Serverless-2016-10-31
Globals:
  Function:
    Runtime: nodejs22.x
    Architectures: [arm64]
Resources:
  ResizeImage: # CPU-bound
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: dist/resize
      Handler: index.handler
      MemorySize: 512
  CallPartnerApi: # waits on the network
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: dist/partner
      Handler: index.handler
      MemorySize: 128
  • Classify the function first. Whether it computes or waits decides more than any rule of thumb, including mine.
  • Start CPU-bound Node.js at 512 MB and I/O-bound at 128 MB. Then move to 1,024 MB if latency matters more than a few per cent on the bill.
  • Measure your own curve with AWS Lambda Power Tuning. It runs your real function at several memory sizes and plots speed against cost, which is my table for your workload.
  • Fix cold starts with a smaller bundle, not more memory. Bundling with esbuild is the cheapest win; my Lambda monorepo post shows the setup.
  • Record cold and warm separately. The REPORT log line includes Init Duration only on cold starts (AWS docs), so split on it before you average anything. I wish I had.
ShareLinkedIn

Liked this?

Get the next field note

New posts and tools on engineering with AI agents and serverless, when they're ready. No schedule, no spam.

RSS feedFollow on LinkedIn

Related reading