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 <= 10
values.length === actions.length
EventEmitter
, 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.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The event emitter problem involves managing events and their associated actions. The brute force approach tackles this by exhaustively examining every possible relationship between events and actions. It's like trying every single connection to see what works.
Here's how the algorithm would work step-by-step:
class EventEmitter:
def __init__(self):
self.event_listeners = {}
def on(self, event_name, listener_function):
if event_name not in self.event_listeners:
self.event_listeners[event_name] = []
self.event_listeners[event_name].append(listener_function)
def emit(self, event_name, data=None):
# Check if any listeners are registered for the given event.
if event_name in self.event_listeners:
# Iterate through all listener functions for the event.
for listener_function in self.event_listeners[event_name]:
listener_function(data)
def remove_listener(self, event_name, listener_function_to_remove):
# Filter out listener_function_to_remove from the list
if event_name in self.event_listeners:
self.event_listeners[event_name] = [
listener_function
for listener_function in self.event_listeners[event_name]
if listener_function != listener_function_to_remove
]
# If the list is now empty, remove the event.
if not self.event_listeners[event_name]:
del self.event_listeners[event_name]
def remove_all_listeners(self, event_name):
# Remove all listeners associated with the specified event.
if event_name in self.event_listeners:
# Delete the key to remove every listener.
del self.event_listeners[event_name]
The event emitter problem involves creating a system where actions (events) can be announced, and other parts of the system can listen for those announcements. The clever part is using a structure that quickly connects announcements to the right listeners, so that we don't have to search all the listeners every time an announcement happens.
Here's how the algorithm would work step-by-step:
class EventEmitter:
def __init__(self):
self.listeners = {}
def on(self, event_name, callback_function):
# Creates a list for event if it does not exist.
if event_name not in self.listeners:
self.listeners[event_name] = []
self.listeners[event_name].append(callback_function)
def emit(self, event_name, *args):
# Prevents error if event does not exist.
if event_name in self.listeners:
# Execute each of callback functions for event.
for callback_function in self.listeners[event_name]:
callback_function(*args)
def off(self, event_name, callback_function):
# Ensures removal only occurs if event exists.
if event_name in self.listeners:
# Remove the specific callback function from the event's listeners.
try:
self.listeners[event_name].remove(callback_function)
except ValueError:
pass
Case | How to Handle |
---|---|
Event name is null or empty string | Throw an IllegalArgumentException or return immediately as event names are required. |
Callback is null | Throw an IllegalArgumentException or skip the callback if it's null to avoid NullPointerException. |
Maximum number of listeners reached (if a limit is implemented) | Either throw an exception indicating the limit is reached, or silently drop the new listener. |
Event emitted with null or undefined arguments | Handle null or undefined arguments gracefully by either filtering them out or passing them as is to the listeners. |
Listener throws an exception during event emission | Catch exceptions within the event emission loop to prevent it from crashing and potentially log the error. |
Unsubscribing a non-existent listener | Silently ignore the unsubscribe request or return false to indicate failure to remove the listener. |
Emitting an event with a very large number of listeners | Consider using asynchronous processing (e.g., setTimeout) to avoid blocking the main thread for too long. |
Circular event emission (event A triggers event B, which triggers event A) | Implement a call stack depth check or a mechanism to detect and prevent infinite recursion to avoid stack overflow. |