FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

Short answer

Node's V8 engine ran out of room in its own JavaScript heap and aborted the process. This is not the same as Lambda's memory limit — V8 sizes its heap from the container's memory, so on a small function the JavaScript heap fills before the container does, and V8 kills itself first.

What it means

Node processes run inside two nested memory limits, and confusing them is why this error is so often "fixed" by a change that does nothing. The outer limit is Lambda's: the container's cgroup, enforced by the kernel with SIGKILL. The inner limit is V8's JavaScript heap, which the engine manages itself and sizes from the memory it can see.

When the inner limit is reached first, V8 does something the kernel never does — it tells you. It prints a summary of the last few garbage collections, a fatal error naming the heap, and a stack trace, then aborts. That is genuinely more helpful than an OOM kill, because you get a line number. The cost is that it can happen while the container still has memory free, which is why a REPORT line can show Max Memory Used comfortably under Memory Size on an invocation that died complaining about memory.

The <--- Last few GCs ---> block that precedes the fatal error is worth reading rather than scrolling past. It shows collection cycles running back to back and reclaiming almost nothing, which distinguishes the two shapes of memory failure. A slow accumulation — a cache that never evicts, a result array that grows with every page — produces exactly that pattern of increasingly desperate collections. A single enormous allocation, like parsing one very large document, tends to fail immediately with no GC thrashing at all. The first calls for bounding what you retain; the second calls for streaming what you read.

Both are worth diagnosing against the whole invocation rather than the fatal line alone. The error tells you the heap filled; your own log lines immediately before it tell you what was being loaded when it did, and the REPORT line tells you whether the container had headroom to spare. Those three facts sit in three different places in a raw CloudWatch stream, and they only mean something together.

Counts

CloudWatch’s Errors metric: The error escaped your handler, so Lambda reports the invocation as failed and CloudWatch’s Errors metric counts it. V8 aborts the process, so the invocation fails and CloudWatch counts it. Note that LogStitch classified the example below from its log level rather than from an extracted error type, because the runtime writes this as a plain ERROR line rather than a Runtime. envelope.

What it looks like in CloudWatch

This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.

CloudWatch Logs Before

7 raw lines, in the order CloudWatch delivered them.

2026-08-29T07:14:31.882Z START RequestId: 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 Version: $LATEST
2026-08-29T07:14:31.886Z 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 INFO Aggregating events window=24h
2026-08-29T07:14:33.401Z 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 INFO Page 1 loaded rows=50000
2026-08-29T07:14:35.902Z 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 INFO Page 2 loaded rows=50000
2026-08-29T07:14:38.117Z 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 ERROR FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
2026-08-29T07:14:38.402Z END RequestId: 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33
2026-08-29T07:14:38.402Z REPORT RequestId: 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 Duration: 6520.11 ms Billed Duration: 6521 ms Memory Size: 1024 MB Max Memory Used: 1024 MB Status: error

LogStitch After

The same lines, grouped into the invocation they belong to.

ErrorMemory ceiling91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33dur 6.52sbilled 6.52smem 1024/1024MBlogs 7
  1. 07:14:31.882PLAT
    START RequestId: 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33 Version: $LATEST
  2. 07:14:31.886INFO
    INFO	Aggregating events window=24h
  3. 07:14:33.401INFO
    INFO	Page 1 loaded rows=50000
  4. 07:14:35.902INFO
    INFO	Page 2 loaded rows=50000
  5. 07:14:38.117ERROR
    ERROR	FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
  6. 07:14:38.402PLAT
    END RequestId: 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33
  7. 07:14:38.402PLAT
    REPORT RequestId: 91b4c7d0-3e55-4a18-b2f6-0d7c9a1e4b33	Duration: 6520.11 ms	Billed Duration: 6521 ms	Memory Size: 1024 MB	Max Memory Used: 1024 MB	Status: error
Error
Status
6.52s
Duration
6.52s
Billed
1024/1024MB
Memory
0%
Headroom
No
Cold start
LogLevelError
Error type

The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.

How to confirm it from the logs

Look for the FATAL ERROR line, and for the <--- Last few GCs ---> block that V8 prints immediately before it. That block is the giveaway: it is V8 reporting that garbage collection ran repeatedly and reclaimed almost nothing, which is the definition of heap exhaustion rather than a sudden spike.

Then compare with the REPORT line. If Max Memory Used is below Memory Size, V8's heap limit was the binding constraint, not Lambda's — the container had room left and the engine still gave up. If they are equal, both ceilings were reached at once.

Causes, most likely first

1

An array or object grows without bound inside the handler

How to confirm

Find the last line logged before the fatal error and look for accumulation in the code that follows — pushing every row of a query into an array, concatenating strings in a loop, or collecting results across pages before returning. If the crash happens later on larger inputs and not at all on small ones, the growth is proportional to the input.

2

A whole file or response is parsed into memory at once

How to confirm

Check for JSON.parse on a large body, await response.text(), or reading an S3 object into a string or buffer. Parsing doubles the peak briefly, because the raw text and the resulting object structure both exist at the same moment — a 200 MB JSON document needs well over 400 MB to parse.

3

Module-scope state accumulates across warm invocations

How to confirm

See whether the failure happens on a container that has already served many requests rather than on a cold start. Anything declared outside the handler persists between invocations; an unbounded cache or a growing array at module scope will eventually fill the heap no matter how small each individual request is.

4

The allocation is small enough that V8's default heap is the real limit

How to confirm

Check the Memory Size. On smaller allocations V8's old-space limit lands below the container limit, so Max Memory Used never reaches Memory Size and the container never appears to be under pressure — yet the heap is full. A REPORT line showing plenty of unused memory alongside a heap fatal is this case.

Fixes

Fix 1

Raise the function's memory, which raises V8's heap with it

V8 derives its heap limit from the memory available to the container, so increasing MemorySize increases the JavaScript heap without touching any code. This is the correct first move when the working set is genuinely that large, and it buys CPU at the same time.

yamlResources:
  TransformFunction:
    Type: AWS::Serverless::Function
    Properties:
      MemorySize: 2048   # was 512; heap scales with the container
Fix 2

Stream the data instead of holding it

The durable fix is to stop peak memory depending on input size. Process records as they arrive so only one chunk is resident at a time, rather than materialising the whole payload and then iterating it.

jsimport { createInterface } from "node:readline";

// Before: the entire body, then the entire parsed array.
// const rows = JSON.parse(await body.transformToString());

// After: one record resident at a time.
const lines = createInterface({ input: body });
for await (const line of lines) {
  await handle(JSON.parse(line));
}
Fix 3

Set the heap limit explicitly when you need it below the container size

Occasionally you want V8 to fail early and predictably rather than push the container to an OOM kill — a heap fatal at least leaves a stack trace, where SIGKILL leaves nothing. --max-old-space-size sets the limit in megabytes.

yamlResources:
  TransformFunction:
    Type: AWS::Serverless::Function
    Properties:
      MemorySize: 2048
      Environment:
        Variables:
          # Fail with a trace at 1.5 GB rather than being killed at 2 GB.
          NODE_OPTIONS: --max-old-space-size=1536

Also seen as

The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory<--- Last few GCs --->lambda javascript heap out of memorynode lambda out of memory

Errors that show up alongside this one, or that people mistake for it.

References

LogStitch finds this automatically, across every invocation in your account.

Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.