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.

category

0

Topics

code_blocks

1

Code langs

hub

0

Related

Practice Focus

Medium · Event Emitter core interview pattern

bolt

Answer-first summary

Design an EventEmitter class that can subscribe, emit, and unsubscribe events, mimicking patterns seen in Node.js or DOM EventTarget.

Interview AiBox logo

Ace coding interviews with Interview AiBox guidance for Event Emitter core interview pattern

Try AiBox Copilotarrow_forward

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).

terminal

Solution

Solution 1

#### TypeScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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'); // []
 */
Event Emitter Solution: Event Emitter core interview pattern | LeetCode #2694 Medium