LeetCode Problem Workspace
Event Emitter
Design an EventEmitter class that can subscribe, emit, and unsubscribe events, mimicking patterns seen in Node.js or DOM EventTarget.
0
Topics
1
Code langs
0
Related
Practice Focus
Medium · Event Emitter core interview pattern
Answer-first summary
Design an EventEmitter class that can subscribe, emit, and unsubscribe events, mimicking patterns seen in Node.js or DOM EventTarget.
Ace coding interviews with Interview AiBoxInterview AiBox guidance for Event Emitter core interview pattern
This problem tests your ability to design an EventEmitter class that mimics event-driven systems like those in Node.js or the DOM EventTarget interface. The solution involves creating a class with methods for subscribing, emitting, and unsubscribing to events. You need to ensure events trigger the correct callbacks, even when arguments are passed, and handle cases where events might have no active subscriptions.
Problem Statement
In this problem, you need to design an EventEmitter class with methods to subscribe, emit, and unsubscribe events. The EventEmitter class should have two main functions: emit and subscribe. The emit method triggers the callbacks for the specified event, while subscribe registers those callbacks for the event. Additionally, the unsubscribe method should allow removal of specific subscriptions by their index.
You are given a list of actions where each action corresponds to a method on the EventEmitter. The task is to process these actions in sequence and output the results for each operation. This is meant to test your understanding of event-driven programming patterns and your ability to implement them efficiently within a class.
Examples
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]]]
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"]]]
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",[]]]
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
Constraints
- 1 <= actions.length <= 10
- values.length === actions.length
- All test cases are valid, e.g. you don't need to handle scenarios when unsubscribing from a non-existing subscription.
- There are only 4 different actions: EventEmitter, emit, subscribe, and unsubscribe.
- The EventEmitter action doesn't take any arguments.
- The 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.
- The subscribe action takes 2 arguments, where the first one is the event name and the second is the callback function.
- The unsubscribe action takes one argument, which is the 0-indexed order of the subscription made before.
Solution Approach
Define the EventEmitter class
Start by defining the EventEmitter class, which will maintain a map of events to their respective callbacks. Each event name maps to an array of callback functions that are registered for that event.
Implement the subscribe method
The subscribe method will allow a callback to be added to the list of functions for a given event. It should return an unsubscribe function that, when called, removes the callback from the event's subscription list.
Handle the emit and unsubscribe methods
The emit method should trigger all callbacks subscribed to the event, passing any arguments passed to emit. The unsubscribe method should remove the callback at a specific index from the list of subscribed callbacks.
Complexity Analysis
| Metric | Value |
|---|---|
| Time | Depends on the final approach |
| Space | Depends on the final approach |
The time complexity of the solution depends on the approach used to store and retrieve event subscriptions. The emit and subscribe methods are typically O(1) for insertion and emission, but iterating over multiple subscriptions or handling array removals can push the complexity to O(n). Memory usage depends on the number of events and their subscriptions, making it O(n) in space where n is the number of events and subscriptions combined.
What Interviewers Usually Probe
- Tests understanding of core event-driven programming concepts.
- Assesses ability to manage dynamic event subscriptions efficiently.
- Evaluates clarity in implementing custom unsubscribe logic.
Common Pitfalls or Variants
Common pitfalls
- Not handling the unsubscribe logic correctly, causing memory leaks.
- Forgetting to trigger callbacks with the correct arguments passed via emit.
- Inefficient subscription management leading to unnecessary performance overhead.
Follow-up variants
- Handling multiple events with varying numbers of subscriptions.
- Managing event emissions with optional arguments.
- Implementing batch subscriptions or emitting multiple events at once.
FAQ
What is the EventEmitter pattern?
The EventEmitter pattern is a design where objects can emit events and other objects can subscribe to these events. It is commonly used in event-driven programming to handle asynchronous actions.
How does the EventEmitter class work in this problem?
The EventEmitter class allows subscribing to events, emitting events that trigger callback functions, and unsubscribing from events. The goal is to manage these operations efficiently.
What are some common issues with event-driven systems?
Common issues include handling multiple subscriptions, managing memory through unsubscribe operations, and ensuring that callbacks receive the correct arguments when events are emitted.
How do I implement the unsubscribe method in EventEmitter?
The unsubscribe method should remove a specific callback from the event's subscription list based on the provided index, ensuring that it doesn't affect other callbacks registered for the same event.
What is the time complexity of this problem?
The time complexity depends on the approach used for storing events and their subscriptions. Emitting or subscribing is typically O(1), but removing an item from a list can be O(n).
Solution
Solution 1
#### TypeScript
type Callback = (...args: any[]) => any;
type Subscription = {
unsubscribe: () => void;
};
class EventEmitter {
private d: Map<string, Set<Callback>> = new Map();
subscribe(eventName: string, callback: Callback): Subscription {
this.d.set(eventName, (this.d.get(eventName) || new Set()).add(callback));
return {
unsubscribe: () => {
this.d.get(eventName)?.delete(callback);
},
};
}
emit(eventName: string, args: any[] = []): any {
const callbacks = this.d.get(eventName);
if (!callbacks) {
return [];
}
return [...callbacks].map(callback => callback(...args));
}
}
/**
* const emitter = new EventEmitter();
*
* // Subscribe to the onClick event with onClickCallback
* function onClickCallback() { return 99 }
* const sub = emitter.subscribe('onClick', onClickCallback);
*
* emitter.emit('onClick'); // [99]
* sub.unsubscribe(); // undefined
* emitter.emit('onClick'); // []
*/