How does Garbage Collection work in Node.js (V8)?
Garbage Collection in Node.js
Node.js uses the V8 JavaScript engine, which employs an automatic garbage collector (GC) to manage memory. Understanding how it works is essential for writing memory-efficient applications.
V8 Memory Structure
V8 divides the heap into two main areas:
| Area | Purpose | GC Strategy |
|---|---|---|
| Young Generation (New Space) | Short-lived objects | Scavenge (minor GC) |
| Old Generation (Old Space) | Long-lived objects | Mark-Sweep-Compact (major GC) |
βββββββββββββββββββββββββββββββββββββββββββ
β V8 Heap β
β ββββββββββββββββ ββββββββββββββββββββ β
β β Young Gen β β Old Gen β β
β β (1-8 MB) β β (up to ~1.5 GB) β β
β β ββββββ¬βββββ β β β β
β β βFromβ To β β β Mark-Sweep- β β
β β β β β β β Compact β β
β β ββββββ΄βββββ β β β β
β ββββββββββββββββ ββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββScavenge (Minor GC) β Young Generation
Works on the Cheney's algorithm (semi-space copying):
- New objects are allocated in the "From" space
- When "From" fills up, live objects are copied to "To" space
- "From" space is cleared entirely
- Spaces swap roles
// Objects start in Young Generation
let temp = { data: 'short-lived' }; // β Young Gen (From space)
temp = null; // eligible for GC on next scavenge- Very fast (only copies live objects, not all)
- Objects that survive 2 scavenges are promoted to Old Generation
Mark-Sweep-Compact (Major GC) β Old Generation
Three-phase process for long-lived objects:
1. Mark Phase
Starting from GC roots (global, stack), the collector traverses the object graph marking all reachable objects.
2. Sweep Phase
All unmarked objects are freed.
3. Compact Phase (optional)
Memory is defragmented by moving live objects together.
Mark: [β][β][β][β][β][β] (β = live, β = dead)
Sweep: [β][ ][β][ ][ ][β]
Compact: [β][β][β][ ]Incremental & Concurrent GC
V8 doesn't stop the world for the entire GC. It uses:
| Technique | Description |
|---|---|
| Incremental marking | Marking done in small steps interleaved with JS execution |
| Concurrent sweeping | Sweeping happens on background threads |
| Concurrent compaction | Some compaction on background threads |
| Lazy sweeping | Pages are swept only when allocation needs them |
Monitoring GC
# Run with GC tracing
node --trace-gc server.js
# Output example:
# [12345:0x1] 52 ms: Scavenge 4.2 (6.3) -> 3.8 (8.3) MB, 1.2 / 0.0 ms
# [12345:0x1] 102 ms: Mark-sweep 8.1 (12.3) -> 6.2 (12.3) MB, 5.1 / 0.0 ms// Expose GC for manual triggering (testing only!)
// node --expose-gc script.js
if (global.gc) {
global.gc(); // Force garbage collection
}
// Using performance hooks
const { PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(`GC: ${entry.kind} - ${entry.duration.toFixed(2)}ms`);
});
});
obs.observe({ entryTypes: ['gc'] });V8 Memory Limits
| Platform | Default Heap Limit |
|---|---|
| 64-bit | ~1.5 GB |
| 32-bit | ~512 MB |
Override with:
node --max-old-space-size=4096 server.js # 4GB
node --max-semi-space-size=64 server.js # 64MB young genBest Practices
| Practice | Why |
|---|---|
| Avoid large long-lived objects | Reduces Old Gen pressure |
Use WeakMap / WeakRef for caches | Allows GC to collect entries |
| Avoid closures that capture large scopes | Prevents unintentional retention |
| Stream large data instead of buffering | Keeps Young Gen small |
Monitor with --trace-gc in staging | Identify GC pauses early |
Key insight: Most performance issues in Node.js are caused by excessive GC pauses in the Old Generation. Keep your Old Generation lean by avoiding unbounded caches, closures over large objects, and always cleaning up references.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.