Debugging Node.js Memory Leaks: Event Listeners & Profiling

Understanding Memory Leaks in Javascript

Node.js uses garbage collection to free memory, but if code retains active references to objects that are no longer needed, the memory cannot be reclaimed. Over time, this leads to performance degradation and crashes.

Case Study: WebSocket Server Crash Loops

A real-time notification service crashed due to running out of memory every 24 hours under a stable load of 10,000 active socket connections. RAM usage steadily climbed without ever dropping.

The Bug: Leaking Socket Event Listeners

By connecting Chrome DevTools to the running Node instance and taking heap snapshots, we identified a massive retention of socket contexts. It turned out that event handlers were subscribing to global events but never unsubscribing when clients disconnected:

// Offending Code
dbEmitter.on('update', (data) => {
    // This closure captures the client socket object!
    socket.emit('data', data);
});

// When client disconnected, the reference to socket remained inside dbEmitter!

The Fix: Proper Cleanup on Close

We modified the connection flow to store a named function and remove the listener on client disconnect:

const onUpdate = (data) => {
    socket.emit('data', data);
};

dbEmitter.on('update', onUpdate);

socket.on('disconnect', () => {
    dbEmitter.off('update', onUpdate); // Correctly free references!
});

Applying this cleanup code resulted in a completely flat RAM consumption curve, preventing future service crashes.

Scroll to Top