Design an EventEmitter class. This interface is similar (but with some differences) to the one found in Node.js or the Event Target interface of the DOM. The EventEmitter should allow for subscribing to events and emitting them.
Your EventEmitter class should have the following two methods:
subscribe are referentially identical.subscribe method should also return an object with an unsubscribe method that enables the user to unsubscribe. When it is called, the callback should be removed from the list of subscriptions and undefined should be returned.Example 1:
Input:
actions = ["EventEmitter", "emit", "subscribe", "subscribe", "emit"],
values = [[], ["firstEvent"], ["firstEvent", "function cb1() { return 5; }"], ["firstEvent", "function cb1() { return 6; }"], ["firstEvent"]]
Output: [[],["emitted",[]],["subscribed"],["subscribed"],["emitted",[5,6]]]
Explanation:
const emitter = new EventEmitter();
emitter.emit("firstEvent"); // [], no callback are subscribed yet
emitter.subscribe("firstEvent", function cb1() { return 5; });
emitter.subscribe("firstEvent", function cb2() { return 6; });
emitter.emit("firstEvent"); // [5, 6], returns the output of cb1 and cb2
Example 2:
Input:
actions = ["EventEmitter", "subscribe", "emit", "emit"],
values = [[], ["firstEvent", "function cb1(...args) { return args.join(','); }"], ["firstEvent", [1,2,3]], ["firstEvent", [3,4,6]]]
Output: [[],["subscribed"],["emitted",["1,2,3"]],["emitted",["3,4,6"]]]
Explanation: Note that the emit method should be able to accept an OPTIONAL array of arguments.
const emitter = new EventEmitter();
emitter.subscribe("firstEvent, function cb1(...args) { return args.join(','); });
emitter.emit("firstEvent", [1, 2, 3]); // ["1,2,3"]
emitter.emit("firstEvent", [3, 4, 6]); // ["3,4,6"]
Example 3:
Input:
actions = ["EventEmitter", "subscribe", "emit", "unsubscribe", "emit"],
values = [[], ["firstEvent", "(...args) => args.join(',')"], ["firstEvent", [1,2,3]], [0], ["firstEvent", [4,5,6]]]
Output: [[],["subscribed"],["emitted",["1,2,3"]],["unsubscribed",0],["emitted",[]]]
Explanation:
const emitter = new EventEmitter();
const sub = emitter.subscribe("firstEvent", (...args) => args.join(','));
emitter.emit("firstEvent", [1, 2, 3]); // ["1,2,3"]
sub.unsubscribe(); // undefined
emitter.emit("firstEvent", [4, 5, 6]); // [], there are no subscriptions
Example 4:
Input:
actions = ["EventEmitter", "subscribe", "subscribe", "unsubscribe", "emit"],
values = [[], ["firstEvent", "x => x + 1"], ["firstEvent", "x => x + 2"], [0], ["firstEvent", [5]]]
Output: [[],["subscribed"],["subscribed"],["unsubscribed",0],["emitted",[7]]]
Explanation:
const emitter = new EventEmitter();
const sub1 = emitter.subscribe("firstEvent", x => x + 1);
const sub2 = emitter.subscribe("firstEvent", x => x + 2);
sub1.unsubscribe(); // undefined
emitter.emit("firstEvent", [5]); // [7]
Constraints:
1 <= actions.length <= 10values.length === actions.lengthEventEmitter, emit, subscribe, and unsubscribe.EventEmitter action doesn't take any arguments.emit action takes between either 1 or 2 arguments. The first argument is the name of the event we want to emit, and the 2nd argument is passed to the callback functions.subscribe action takes 2 arguments, where the first one is the event name and the second is the callback function.unsubscribe action takes one argument, which is the 0-indexed order of the subscription made before.The key idea in #2694 Event Emitter is to simulate a simple publish–subscribe system. You need a structure that allows multiple callbacks to subscribe to a specific event and then be triggered whenever that event is emitted.
A common approach is to maintain a hash map where each event name maps to a list (or array) of callback functions. When a user calls subscribe(eventName, callback), the callback is appended to the list for that event. The method typically returns an unsubscribe function that removes the specific callback from the list.
When emit(eventName, args) is called, the emitter simply iterates through the stored callbacks for that event and executes them with the provided arguments, collecting their return values.
This design keeps event lookup efficient and ensures callbacks execute in registration order. Using a hash map ensures quick access to event listeners, while array iteration handles callback execution efficiently.
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| Subscribe | O(1) | O(1) |
| Emit Event | O(n) | O(1) |
| Unsubscribe | O(n) | O(1) |
NeetCode
This approach uses a HashMap (or similar data structure) to maintain a list of listeners (callbacks) for each event. When an event is emitted, we iterate through the list of registered callbacks and invoke them. This list enables easy addition and removal of callbacks (for unsubscribe functionality) and supports ordered invocation on event emission.
Time Complexity: O(n) for emit, where n is the number of callbacks for the event. O(1) for subscribe and unsubscribe.
Space Complexity: O(m), where m is the number of events and subscribed callbacks.
1class EventEmitter {
2 constructor() {
3 this.events = {};
4 }
5
6 subscribe(
The EventEmitter class contains a subscribe method to add event listeners to a dictionary, with event names as keys and arrays of callbacks as values. The emit method retrieves the listener list for a given event and executes each callback with provided arguments, returning an array of results. The unsubscribe feature is implemented by removing a callback from the array using an index.
This approach employs the Observer pattern, common in design paradigms where a subject maintains a list of observers that react to state changes or events. In this context, the EventEmitter represents the Subject, and listeners are Observers.
Time Complexity: O(n) for notifying listeners (n is the number of listeners).
Space Complexity: O(m), where m consists of stored observers.
1class Observer:
2 def __init__(self,
Watch expert explanations and walkthroughs
Jot down your thoughts, approach, and key learnings
Yes, event emitter style problems are common in frontend and JavaScript-focused interviews. They test understanding of event-driven design, closures, and data structures used in real-world frameworks.
The optimal approach uses a hash map that maps each event name to a list of callback functions. This allows quick subscription and efficient event emission by iterating through the registered callbacks for that event.
The emit function retrieves the list of callbacks associated with a given event and executes each one with the provided arguments. The results of these callbacks are typically collected and returned in order.
A hash map (or dictionary) combined with arrays or lists works best. The map stores event names as keys and lists of callbacks as values, enabling fast lookups and ordered execution of handlers.
The Python solution uses the same Observer pattern concept, where Observer classes act as listeners with a method to execute their logic. The EventEmitter keeps track of observers per event, using them to notify observed changes.