Watch 10 video solutions for Event Emitter, a medium level problem. This walkthrough by NeetCodeIO has 9,455 views views. Want to try solving it yourself? Practice on FleetCode or read the detailed text solution.
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.Problem Overview: Design an Event Emitter that allows clients to subscribe to an event and emit that event later. Each event can have multiple listeners (callbacks). When emit(eventName, args) is called, every subscribed callback for that event should run in order and return its result.
Approach 1: Using a HashMap for Event Listeners (Emit: O(n) time, O(n) space)
The most practical design stores event listeners in a hash map where the key is the event name and the value is a list of callback functions. When you call subscribe(event, callback), append the callback to the list for that event. The subscribe method also returns an unsubscribe function that removes the callback from the list when invoked.
When emit(event, args) runs, look up the event in the map and iterate through its callback list. Execute each function with the provided arguments and collect the returned values into a result array. The emit operation takes O(n) time where n is the number of listeners registered for that event, while subscription and lookup remain O(1) on average due to the hash map.
This approach mirrors how real-world event systems work in frameworks and Node.js. The combination of constant-time event lookup and sequential callback execution makes it efficient and easy to reason about. It relies heavily on the hash map data structure for quick event dispatch.
Approach 2: Using the Observer Pattern (Emit: O(n) time, O(n) space)
This problem is also a direct implementation of the Observer Pattern, a classic design pattern where a subject maintains a list of observers and notifies them when an event occurs. Each observer registers itself with the subject (the emitter), and the emitter broadcasts updates whenever an event is triggered.
Internally the implementation still maintains a collection of listeners per event, but the design is structured around objects representing observers and subscription handles. Each subscription object encapsulates the callback and provides an unsubscribe() method. When the event fires, the emitter iterates through all observers and invokes their update method.
The time complexity of emitting remains O(n) since every registered listener must run. Space complexity is also O(n) because all listeners must be stored in memory. This approach is preferred in systems that emphasize clean architecture and modular event-driven design. It closely aligns with system design principles and the observer pattern used in UI frameworks, messaging systems, and reactive programming.
Recommended for interviews: The hash map listener registry is the expected solution. It demonstrates understanding of event-driven design while keeping operations efficient. Showing the brute design idea first (storing callbacks per event) proves you understand the problem model, while implementing the hash map based dispatcher shows the engineering skill interviewers want to see.
| Approach | Time | Space | When to Use |
|---|---|---|---|
| HashMap for Event Listeners | Subscribe: O(1), Emit: O(n) | O(n) | General solution for implementing event emitters with fast event lookup |
| Observer Pattern Implementation | Emit: O(n) | O(n) | When modeling structured event-driven architectures or design pattern discussions |