Node.js 'heap out of memory' with large datasets in production
This error hits when processing large JSON arrays or CSV files in Node. You hit the default 1.4 GB memory limit. Here's how to bump it safely.
When this hits
You're running a Node.js process in production that reads a JSON file with 500,000 records or a CSV export from a database. The script runs fine for a few minutes, then dies with:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
It's common when doing bulk imports, batch ETL jobs, or API responses that return large arrays. Node's V8 engine gives you about 1.4 GB of heap by default on 64-bit systems. Your dataset just won't fit.
What's actually happening
JavaScript's garbage collector can't free memory fast enough. When you load a big JSON array, Node stores every object in the heap. Even if you stream the file, intermediate structures (like arrays of parsed objects) pile up. The GC runs, but it's fighting against allocations that keep coming. At some point, V8 says "I can't allocate another byte," and the process crashes.
The real fix is not just increasing memory — it's understanding that your default 1.4 GB limit is fine for most apps but not for data pipelines. You either bump the limit or switch to streaming with smaller batches.
Step-by-step fix
Step 1: Find your current heap usage
First, see where you stand. Add this line to your code and run it:
console.log(`Heap used: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`);
This prints how much heap your script consumes just before it crashes. For a 500,000-object JSON array, you'll often see 1.3–1.5 GB — right at the limit.
Step 2: Increase the heap limit via command line
Stop your process. Add the --max-old-space-size flag. This sets the max heap in megabytes.
node --max-old-space-size=4096 your-script.js
That gives you 4 GB. For production, pick a number that leaves room for other processes. On a server with 8 GB RAM total, 4 GB is safe. On a dedicated box, 6–7 GB works.
After running, you should see the process finish without the crash. Check the memory usage again — if it's near the new limit, bump it higher or optimize your data handling.
Step 3: Set this permanently for production
Don't rely on manual flags. Update your start script or Docker command.
For package.json scripts:
"scripts": {
"start": "node --max-old-space-size=4096 your-app.js"
}
For Docker: Add to ENTRYPOINT or CMD:
CMD ["node", "--max-old-space-size=4096", "your-app.js"]
For PM2: In ecosystem.config.js:
module.exports = {
apps: [{
name: 'app',
script: 'your-app.js',
node_args: '--max-old-space-size=4096'
}]
};
After updating, restart the process. Verify with a log line at startup:
console.log(`Max old space size: ${require('v8').getHeapStatistics().heap_size_limit / 1024 / 1024} MB`);
You should see 4096 MB (or whatever you set).
If it still fails
Check for memory leaks
If you increased the heap but it still crashes after a while, your code might not be freeing objects. Common culprits:
- Storing references to every record in a global array instead of processing one at a time.
- Using
letinside loops with closures that hold onto objects. - Not nullifying large objects after you're done.
Run with --inspect and open Chrome DevTools to take heap snapshots. Compare two snapshots — if memory keeps climbing without dropping, you've got a leak.
Switch to streaming
The real solution for huge datasets is not to load everything at once. Use libraries like JSONStream or csv-parser that emit each row as a stream event. Example with JSONStream:
const fs = require('fs');
const JSONStream = require('JSONStream');
const stream = fs.createReadStream('huge.json');
const parser = JSONStream.parse('*');
stream.pipe(parser)
.on('data', (record) => {
// process one record — memory stays low
});
This keeps heap usage under 200 MB even for millions of records.
Use child processes
If you must process everything in memory (like sorting or aggregating), split the work across child processes. Each child gets its own heap. Use Node's child_process.fork() and pass data via IPC.
Summary
Start by bumping --max-old-space-size to 4 GB. That buys you time. Then audit your code for leaks and switch to streaming where possible. You'll avoid the crash and keep your production pipeline running.
Was this solution helpful?