Skip to content
Node.js nd events 4 min read

Managing Event Listeners

Attaching listeners to an EventEmitter is easy; the harder part is taking them off again and noticing when you forgot to. Every listener you register holds a reference to its callback — and often to whatever that callback closes over — so listeners that outlive their usefulness keep memory pinned and quietly pile up. Node.js gives you a full toolkit to remove, count, and inspect listeners, plus a built-in early-warning system for the most common leak. This page walks through removeListener/off, removeAllListeners, listenerCount, eventNames, prependListener, and the maxListeners warning.

Removing a single listener with removeListener and off

To remove a listener you must pass the exact same function reference you registered. That is the one rule people trip over: an inline arrow function or a .bind() call produces a new function each time, so you can never remove it. Keep a named reference if you intend to detach later.

emitter.off(event, listener) is an alias for emitter.removeListener(event, listener) — they are identical. off was added in Node 10 to mirror the DOM and is the preferred spelling in modern code.

import { EventEmitter } from 'node:events';

const bus = new EventEmitter();

const onData = (chunk) => console.log('got', chunk);

bus.on('data', onData);
bus.emit('data', 'first'); // logged

bus.off('data', onData);   // detach (same as removeListener)
bus.emit('data', 'second'); // nothing happens

console.log('listeners left:', bus.listenerCount('data'));

Output:

got first
listeners left: 0

An inline bus.on('data', (c) => console.log(c)) can never be removed by reference — there is nothing to pass to off(). If a listener needs to come off later, give it a name.

If the same function is registered more than once, a single off() call removes only the most recently added instance; call it repeatedly to clear all copies.

Removing all listeners

emitter.removeAllListeners([event]) strips every listener at once. With an event name it clears only that event; with no argument it clears all events on the emitter. The argument-less form is blunt — it will also remove listeners other code (or Node itself) attached, so reserve it for teardown of emitters you fully own.

const bus = new EventEmitter();
bus.on('open', () => {});
bus.on('close', () => {});
bus.on('close', () => {});

bus.removeAllListeners('close'); // only 'close' listeners go
console.log(bus.listenerCount('open'));  // 1
console.log(bus.listenerCount('close')); // 0

bus.removeAllListeners();         // wipe everything
console.log(bus.eventNames());    // []

Output:

1
0
[]

Inspecting listeners: listenerCount, eventNames, and listeners

Sometimes you need to know what is currently attached — for diagnostics, conditional cleanup, or tests. Three methods cover this:

MethodReturnsUse it to
emitter.listenerCount(event)number of listeners for eventcheck whether anyone is listening before doing expensive work
emitter.eventNames()array of event names with listenersaudit which events are wired up
emitter.listeners(event)array of the listener functionsinspect or copy the handlers themselves
emitter.rawListeners(event)listeners including once wrapperssee once listeners before they fire
const bus = new EventEmitter();
bus.on('login', () => {});
bus.once('shutdown', () => {});

console.log(bus.eventNames());           // ['login', 'shutdown']
console.log(bus.listenerCount('login')); // 1

if (bus.listenerCount('metrics') === 0) {
  console.log('no metrics listeners — skip computing them');
}

Output:

[ 'login', 'shutdown' ]
1
EventEmitter [skip metrics]

There is also a static form, EventEmitter.listenerCount(emitter, event), but it is deprecated — prefer the instance method shown above.

Controlling order with prependListener

Listeners normally fire in registration order. When you need a handler to run before ones already attached — say, logging or validation that must precede everything else — use prependListener (and prependOnceListener for a one-shot version). It adds the listener to the front of the queue.

const bus = new EventEmitter();

bus.on('request', () => console.log('2: handle'));
bus.prependListener('request', () => console.log('1: log first'));

bus.emit('request');

Output:

1: log first
2: handle

The maxListeners warning: catching leaks early

Because forgotten listeners are the classic Node memory leak, every emitter warns once it crosses a threshold — 10 listeners for a single event by default. Node prints a MaxListenersExceededWarning to stderr but does not throw; the listeners still work. The warning is a hint that you are probably adding handlers in a loop or on every request without ever removing them.

const bus = new EventEmitter();
for (let i = 0; i < 11; i++) {
  bus.on('tick', () => {});
}

Output:

(node:12345) MaxListenersExceededWarning: Possible EventEmitter memory leak
detected. 11 tick listeners added to [EventEmitter]. Use emitter.setMaxListeners()
to increase limit

When a high count is genuinely legitimate, raise the ceiling per-emitter with emitter.setMaxListeners(n) (use 0 or Infinity to disable the check), or change the process-wide default through EventEmitter.defaultMaxListeners. Prefer the per-emitter setter — bumping the global default hides leaks everywhere.

import { EventEmitter } from 'node:events';

bus.setMaxListeners(20);          // raise just this emitter
console.log(bus.getMaxListeners()); // 20

EventEmitter.defaultMaxListeners = 15; // affects all NEW emitters

Raising maxListeners silences the symptom, not the cause. Before increasing the limit, confirm you are actually removing listeners on cleanup — most “leaks” are a missing off() in a disconnect or unmount path.

Best Practices

  • Register removable listeners as named functions so you have a reference to pass to off().
  • Prefer off() over removeListener() in new code — they behave identically and off reads cleaner.
  • Always pair a on() in setup code with an off() (or removeAllListeners) in the matching teardown path.
  • Use once() for one-shot events instead of manually removing the listener after it fires.
  • Treat a MaxListenersExceededWarning as a bug to investigate, not noise to suppress with setMaxListeners.
  • Call removeAllListeners() with no argument only on emitters you fully own, never on shared or core objects.
  • Use listenerCount() to short-circuit expensive work when nobody is listening for the result.
Last updated June 14, 2026
Was this helpful?