DEV Community

Nikolai Maslov
Nikolai Maslov

Posted on

An unusual interview task that turned out to be very common

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:

Event emitter task 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.

Event emitter subscribe method

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:

Event emitter unsubscribe method


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:

Event emitter emit method


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?

Event emitter final implementation

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);
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)