emitter.off()
Remove one previously registered listener from an event.
emitter.off(eventName, listener)- Added in Node.js v10.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$eventName | <string> | <symbol> | The name of the event. |
$listener | <Function> | The exact function reference that was passed to on() or once(). |
Return value
<EventEmitter>A reference to the emitter, so calls can be chained.
Errors
Throws ERR_INVALID_ARG_TYPE if listener is not a function. Removing a listener that was never registered is a no-op, not an error.
off() is an alias for removeListener(): it deletes at most one matching listener from the array for eventName. It works on once() registrations too — Node unwraps the internal one-shot wrapper for you.
When to reach for it
Any time a subscription's lifetime is shorter than the emitter's. A request handler subscribing to a process-wide bus, a component watching a shared config object, a timeout listener that should go away once the real event lands. If the emitter dies with the subscriber, you do not need off() at all.
What trips people up
Identity, not equality. Removal matches by function reference, so you must keep the exact function you registered. An inline arrow — bus.on('data', (c) => log(c)) — creates a brand-new function that nothing else holds, and it can never be removed individually. Writing an identical-looking arrow in the off() call removes nothing and reports no error.
Also, one call removes one instance. If the same function was added three times, you need three off() calls. And a removal performed during an in-flight emit() does not affect that emit — the listener array was already snapshotted.
Example
When to use it
- Detach a per-request listener from an app-wide bus when the response closes
- Cancel a pending 'ready' handler once a timeout has already rejected the operation
- Clear the losing side of a race between 'success' and 'error' listeners
Frequently asked questions
What is the difference between off() and removeListener() in Node.js?
Nothing — off() is an alias added in Node 10.0.0 and both remove a single matching listener. off() is the shorter, more common spelling in modern code.
How do I remove an anonymous arrow function listener?
You cannot remove it individually — off() matches by reference and you never kept one. Assign the arrow to a variable before passing it to on(), or fall back to emitter.removeAllListeners(eventName) to clear the whole event, which also removes listeners you did not add.
Why is my listener still firing after calling off()?
Usually because the function reference does not match, because the same function was registered more than once and only one copy was removed, or because the removal happened during an in-flight emit() that had already snapshotted the listener array. Check emitter.listenerCount(eventName) right after the off() call to see which.
More examples
Duplicates need one off() each
Counts go 3, then 2, then 0. Each off() call removes a single instance, starting from the most recently added one, so accidental duplicate subscriptions cannot be undone with a single removal. removeAllListeners('change') is the blunt alternative.
const { EventEmitter } = require('node:events');
const bus = new EventEmitter();
const save = () => console.log('save');
bus.on('change', save);
bus.on('change', save);
bus.on('change', save);
console.log('registered:', bus.listenerCount('change'));
bus.off('change', save);
console.log('after one off():', bus.listenerCount('change'));
while (bus.listenerCount('change') > 0) bus.off('change', save);
console.log('after draining:', bus.listenerCount('change'));Scoping a subscription to a request
A named onTick closure is the only thing that makes removal possible here. Once the client disconnects, res 'close' fires and the listener count returns to its baseline. Skip the off() and Node prints a MaxListenersExceededWarning after the eleventh concurrent request while memory grows unbounded.
const http = require('node:http');
const { EventEmitter } = require('node:events');
const bus = new EventEmitter();
setInterval(() => bus.emit('tick', Date.now()), 1000).unref();
http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'text/event-stream' });
const onTick = (t) => res.write(`data: ${t}\n\n`);
bus.on('tick', onTick);
// Without this line the process leaks one listener per request.
res.on('close', () => {
bus.off('tick', onTick);
console.log('cleaned up, listeners now:', bus.listenerCount('tick'));
});
}).listen(3000);
Discussion