Introduction
Node.js EventEmitter defaults to a maximum of 10 listeners per event. When more listeners are added, it emits a MaxListenersExceededWarning. This is a safety mechanism - in most cases, adding more than 10 listeners indicates a bug where listeners are being added in a loop without being removed, causing a memory leak.
Symptoms
(node:12345) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 data listeners added- Warning includes
Use emitter.setMaxListeners() to increase limit - Memory usage grows over time as listeners accumulate
- Process eventually runs out of memory
- Warning triggered by streams, HTTP servers, or custom event emitters
(node:12345) MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 error listeners added to [Server].
Use emitter.setMaxListeners() to increase limit
at _addListener (node:events:587:17)
at Server.addListener (node:events:605:10)Common Causes
- Adding event listeners inside a loop without removing old ones
- Creating a new listener for each HTTP request on a shared emitter
- Not removing listeners when components unmount or disconnect
- Third-party libraries adding listeners without cleanup
- Re-subscribing to events after reconnection without unsubscribing first
Step-by-Step Fix
- 1.Find the source of listener accumulation:
- 2.```bash
- 3.# Run with verbose warnings
- 4.NODE_OPTIONS="--trace-warnings" node app.js
# Or in code process.on('warning', (warning) => { console.warn('Warning stack:', warning.stack); }); ```
- 1.Remove listeners when no longer needed:
- 2.```javascript
- 3.// WRONG - listeners accumulate
- 4.connections.forEach(conn => {
- 5.server.on('close', () => conn.cleanup()); // New listener each time
- 6.});
// CORRECT - use named function for removal function handleServerClose() { connections.forEach(conn => conn.cleanup()); } server.on('close', handleServerClose);
// Or use once() for single-execution listeners server.once('close', () => { connections.forEach(conn => conn.cleanup()); }); ```
- 1.Increase limit when legitimately needed:
- 2.```javascript
- 3.const EventEmitter = require('events');
- 4.const emitter = new EventEmitter();
// Increase for this specific emitter emitter.setMaxListeners(50);
// Or set globally (not recommended) EventEmitter.defaultMaxListeners = 50;
// Or set to 0 for unlimited (use with caution) emitter.setMaxListeners(0); ```
- 1.Use listener count monitoring:
- 2.```javascript
- 3.const EventEmitter = require('events');
class MonitoredEmitter extends EventEmitter {
on(event, listener) {
const count = this.listenerCount(event);
if (count > 5) {
console.warn(Warning: ${count} listeners already on '${event}');
}
return super.on(event, listener);
}
}
const emitter = new MonitoredEmitter(); ```
- 1.Clean up listeners with AbortController (Node.js 15+):
- 2.```javascript
- 3.const controller = new AbortController();
// All listeners attached with this signal are removed together emitter.on('data', handler1, { signal: controller.signal }); emitter.on('data', handler2, { signal: controller.signal }); emitter.on('error', errorHandler, { signal: controller.signal });
// Remove ALL listeners at once controller.abort(); // All three listeners removed cleanly ```
Prevention
- Always use
once()instead ofon()for single-execution listeners - Remove listeners in cleanup/destroy methods
- Use
AbortControllerfor batch listener management (Node 15+) - Monitor
emitter.listenerCount(event)in production - Set
--trace-warningsin development to catch issues early - Never blindly increase
defaultMaxListenerswithout understanding why - Use
events.on()async iterator pattern instead ofemitter.on()for streams