Once during an interview, I came across a task that at that moment seemed unusual and even exciting. Later, I realized it was actually quite common — but still, I learned something interesting from solving it.
The task:
👉 Implement an EventEmitter class with the following requirements:
Ability to subscribe (on) multiple callbacks to the same event
Ability to unsubscribe (off) even if the callback is anonymous
Emit an event with arguments and call all subscribed callbacks
Here’s the starting point:
Step 1 — on
method
We need to store all callbacks for a given event name in an array. If it doesn’t exist yet, we initialize it.
To support unsubscribing anonymous functions, on will return an unsubscribe function.
This way, even anonymous functions can be unsubscribed.
It also allows us to easily extend listener with additional options later (e.g., once, priority).
Step 2 — off
method
Unsubscribing means filtering out a matching listener. Since we might pass either the original function or the listener object, we handle both cases:
Step 3 — emit
method
We simply call all subscribed functions with provided arguments.
⚠️ But here’s the tricky part: if a listener unsubscribes itself while executing, we could run into problems because the array is modified during iteration.
Solution → work on a copy of the array:
Final Implementation (Code attached below)
✅ This solution supports multiple listeners, safe unsubscribing (even for anonymous functions), and avoids issues when modifying the array during execution.
It’s also easy to extend with extra features like once listeners or event priorities.
What would you add or change in this EventEmitter? How would you improve it for real-world applications?
class EventEmitter {
events = {};
on(name, fn) {
if (!this.events[name]) {
this.events[name] = [];
}
const listener = { fn };
this.events[name].push(listener);
return () => this.off(name, listener);
}
off(name, listenerOrFn) {
if (!this.events[name]) return;
const predicate = (listener) =>
listener === listenerOrFn || listener.fn === listenerOrFn;
this.events[name] = this.events[name].filter(l => !predicate(l));
}
emit(name, ...args) {
const listeners = this.events[name];
if (!listeners) return;
listeners.slice().forEach(listener => {
listener.fn(...args);
});
}
}
Top comments (0)