Talentd
FleetCode
Make and follow sheets.Sign in

Event Emitter solution and explanation

Medium8 min read74.1% acceptanceUpdated 9 Apr 2026
Event Emitter - Leetcode 2694 - JavaScript 30-Day ChallengeNeetCodeIO · 9:09 · 9K viewsWatch on YouTube
9 more videos on this problem
  1. Event Emitter - Leetcode 2694 - JavaScript 30-Day ChallengeNeetCodeIO · 9:09 · 9K views
  2. Event Emitter | Leetcode 2694 | Classes | 30 Days of JavaScript #javascript #leetcodeLearn With Chirag · 16:47 · 2K views
  3. JavaScript Challenge Day 27: EventEmitter - LeetCode Task 2694vanAmsen · 7:04 · 1K views
  4. #day28 |2694. Event Emitter | 30 Days of JavaScript | Leetcode-2694| 30 Days ChallengeCoding with Ankita · 30:54 · 560 views
  5. TypeScript Tutorial: Building an EventEmitter Class | vanAmsen Coding - 2694vanAmsen · 7:03 · 340 views
  6. Event Emitter - LeetCode 2694 - JavaScript - JS 30 Day Challenge Day 26Alpha-Code · 17:57 · 208 views
  7. 2694. Event Emitter - Day 27/30 Leetcode Javascript ChallengeProgramming Live with Larry · 16:37 · 163 views
  8. #Leetcode 2694 Event Emitter 🚀 Day 28 👨‍💻 30Days of JavaScript Challenge LeetCodeRajat Talesra · 11:05 · 97 views
  9. 2694. Event Emitter | JavaScript Pub/Sub PatternCodeCrafting with Vishnu · 18:50 · 70 views
  10. 2694 JAvaScript Event EmittermanjeetVlogs · 14:48 · 58 views

The problem

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 - This method takes in two arguments: the name of an event as a string and a callback function. This callback function will later be called when the event is emitted.
    An event should be able to have multiple listeners for the same event. When emitting an event with multiple callbacks, each should be called in the order in which they were subscribed. An array of results should be returned. You can assume no callbacks passed to subscribe are referentially identical.
    The 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.
  • emit - This method takes in two arguments: the name of an event as a string and an optional array of arguments that will be passed to the callback(s). If there are no callbacks subscribed to the given event, return an empty array. Otherwise, return an array of the results of all callback calls in the order they were subscribed.

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

Approach overview

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 1: Approach 1: Using a HashMap for Event Listeners

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.

class EventEmitter:
    def __init__(self):
        self.events = {}

    def subscribe(self, event, callback):
        if event not in self.events:
            self.events[event] = []
        self.events[event].append(callback)
        index = len(self.events[event]) - 1

        def unsubscribe():
            self.events[event].pop(index)

        return type('Subscription', (object,), {'unsubscribe': unsubscribe})()

    def emit(self, event, args=[]):
        if event in self.events:
            return [callback(*args) for callback in self.events[event]]
        return []
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class EventEmitter {
    private Map<String, List<Runnable>> events;

    public EventEmitter() {
        events = new HashMap<>();
    }

    public Subscription subscribe(String event, Runnable callback) {
        if (!events.containsKey(event)) {
            events.put(event, new ArrayList<>());
        }
        List<Runnable> listeners = events.get(event);
        listeners.add(callback);

        return new Subscription(() -> listeners.remove(callback));
    }

    public List<Object> emit(String event, Object... args) {
        List<Object> results = new ArrayList<>();
        if (events.containsKey(event)) {
            for (Runnable callback : events.get(event)) {
                results.add(callback);
            }
        }
        return results;
    }

    class Subscription {
        private Runnable unsubscribe;

        Subscription(Runnable unsubscribe) {
            this.unsubscribe = unsubscribe;
        }

        public void unsubscribe() {
            unsubscribe.run();
        }
    }
}
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>

class EventEmitter {
public:
    using Callback = std::function<void()>;
    using CallbackList = std::vector<Callback>;

    void subscribe(const std::string& event, Callback cb) {
        events[event].push_back(cb);
    }

    void emit(const std::string& event) {
        if (events.find(event) != events.end()) {
            for (const auto& cb : events[event]) {
                cb();
            }
        }
    }

private:
    std::unordered_map<std::string, CallbackList> events;
};
class EventEmitter {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    const idx = this.events[event].length;
    this.events[event].push(callback);
    return {
      unsubscribe: () => {
        this.events[event].splice(idx, 1);
      }
    };
  }

  emit(event, args = []) {
    if (!this.events[event]) {
      return [];
    }
    return this.events[event].map(callback => callback(...args));
  }
}
using System;
using System.Collections.Generic;

public class EventEmitter {
    private Dictionary<string, List<Action>> events;

    public EventEmitter() {
        events = new Dictionary<string, List<Action>>();
    }

    public Subscription Subscribe(string event, Action callback) {
        if (!events.ContainsKey(event)) {
            events[event] = new List<Action>();
        }
        events[event].Add(callback);

        return new Subscription(() => events[event].Remove(callback));
    }

    public List<object> Emit(string event, params object[] args) {
        var results = new List<object>();
        if (events.ContainsKey(event)) {
            foreach (var callback in events[event]) {
                callback();
            }
        }
        return results;
    }

    public class Subscription {
        private Action unsubscribe;

        public Subscription(Action unsubscribe) {
            this.unsubscribe = unsubscribe;
        }

        public void Unsubscribe() {
            unsubscribe();
        }
    }
}

The EventEmitter class in Python leverages a dictionary (or dict) to store event names and corresponding callbacks. The subscribe method adds callbacks to the list for each event, and returns an object with an unsubscribe method. The emit method checks for event listeners and executes them with the given arguments.

Complexity

Time Complexity: O(n) for emit, with n being the number of callbacks per event. O(1) for subscribe and unsubscribe.
Space Complexity: O(m), with m representing the total number of callbacks.

Try this approach in the editor

Approach 2: Approach 2: Using Observer Pattern

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.

class Observer:
    def __init__(self, callback):
        self.callback = callback

    def notify(self, args):
        return self.callback(*args)

class EventEmitter:
    def __init__(self):
        self.observers = {}

    def subscribe(self, event, callback):
        observer = Observer(callback)
        if event not in self.observers:
            self.observers[event] = []
        self.observers[event].append(observer)

        def unsubscribe():
            self.observers[event] = [o for o in self.observers[event] if o != observer]

        return type('Subscription', (object,), {'unsubscribe': unsubscribe})()

    def emit(self, event, args=[]):
        if event in self.observers:
            return [observer.notify(args) for observer in self.observers[event]]
        return []
class Observer {
  constructor(callback) {
    this.callback = callback;
  }

  notify(args) {
    return this.callback(...args);
  }
}

class EventEmitter {
  constructor() {
    this.observers = {};
  }

  subscribe(event, callback) {
    const observer = new Observer(callback);
    if (!this.observers[event]) this.observers[event] = [];
    this.observers[event].push(observer);

    return {
      unsubscribe: () => {
        this.observers[event] = this.observers[event].filter(obs => obs !== observer);
      }
    };
  }

  emit(event, args = []) {
    if (!this.observers[event]) return [];
    return this.observers[event].map(observer => observer.notify(args));
  }
}

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.

Complexity

Time Complexity: O(n) for notifying all registered observers.
Space Complexity: O(m), governed by added observers.

Try this approach in the editor

Approach 3: Default Approach

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'); // []
 */
Try this approach in the editor

Complexity comparison

ApproachComplexity
Approach 1: Using a HashMap for Event Listeners

Time Complexity: O(n) for emit, with n being the number of callbacks per event. O(1) for subscribe and unsubscribe.
Space Complexity: O(m), with m representing the total number of callbacks.

Approach 2: Using Observer Pattern

Time Complexity: O(n) for notifying all registered observers.
Space Complexity: O(m), governed by added observers.

Default Approach–

Complexity analysis

ApproachTimeSpaceWhen to Use
HashMap for Event ListenersSubscribe: O(1), Emit: O(n)O(n)General solution for implementing event emitters with fast event lookup
Observer Pattern ImplementationEmit: O(n)O(n)When modeling structured event-driven architectures or design pattern discussions

Frequently asked questions

Is Event Emitter easy or hard?

Event Emitter is considered a medium-level problem because it mixes data structures with system design concepts. The implementation itself is straightforward, but handling subscriptions, unsubscriptions, and ordered callback execution requires careful thinking.

Event Emitter Python/Java solution

In Python or Java, the same idea applies: store a Map from event names to lists of callbacks. The subscribe method adds a callback to the list, while emit iterates through the list and executes each function. The overall complexity remains O(1) for subscription and O(n) for emission.

How to solve Event Emitter in O(n)?

Use a hash map that maps event names to arrays or lists of callbacks. When emit is called, retrieve the list for that event and iterate through each callback while passing the provided arguments. Since every listener runs once, the emit operation naturally takes O(n) time.

What is the best approach for Event Emitter?

The best approach stores event listeners in a hash map where the key is the event name and the value is a list of callbacks. Subscribing adds a function to the list, and emitting an event iterates through that list and executes each callback. This keeps event lookup O(1) and emission O(n) where n is the number of listeners.

Is Event Emitter asked at Google/Amazon/Meta?

Event-driven design questions appear in interviews at companies like Google, Amazon, and Meta, especially for frontend and systems roles. Variants of the Event Emitter problem test understanding of callbacks, subscription management, and observer-style architectures.

What data structure is used in Event Emitter?

The core data structure is a hash map (dictionary) that maps event names to a list of callback functions. This enables constant-time event lookup and efficient iteration over all listeners during emission.

What is the time complexity of Event Emitter?

Subscription typically runs in O(1) time because the listener is appended to a list associated with the event name. Emitting an event takes O(n) time since every registered callback must be executed. Space complexity is O(n) for storing all listeners.

Comments

Now write it yourselfClose this page and solve Event Emitter in the editor, without looking.
Practise Event Emitter