Compile Ready
Module 14 · Machine Coding

Build: Event Emitter

Intermediate11m read30m practice41m total
Machine CodingPubSubEventsDesign Patterns

Introduction

An Event Emitter implements publish-subscribe in a few methods: on, off, once, and emit. It is small enough to code in an interview but rich enough to expose API design choices.

A robust emitter handles listener removal, one-time listeners, duplicate listeners, listener mutation during emit, and ergonomic unsubscribe functions.

Why This Matters

Emitters appear in Node.js, UI frameworks, analytics SDKs, sockets, and plugin systems. Interviewers like this problem because the happy path is easy, while edge cases reveal whether you understand arrays, references, closures, and mutation safety.

Theory

Data model

Use a Map from event name to an array of listener functions. Arrays preserve registration order, which most event systems guarantee. on pushes a listener and returns an unsubscribe function. off removes a listener by reference.

Mutation safety

If a listener removes another listener during emit, iterating the original array can skip or duplicate callbacks. The safe approach is to copy the listener array before calling functions. That makes each emit operate on the listener snapshot that existed at the start.

once design

once wraps the original listener in a function that unsubscribes itself before invoking the user callback. Store a _original reference on the wrapper so off(event, original) can remove a once-listener before it fires.

Visual Diagrams

Emitter listener table
events Map
  login  -> [listenerA, onceWrapper]
  logout -> [listenerB]

emit login
  copy listeners
  call listenerA
  call onceWrapper -> removes itself

Copying listeners before emit makes mutation during callbacks predictable.

Code Examples

Complete EventEmitter

This implementation preserves listener order, supports once, and returns unsubscribe functions.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1function EventEmitter() {
2 this.events = new Map();
3}
4
5EventEmitter.prototype.on = function (eventName, listener) {
6 if (!this.events.has(eventName)) {
7 this.events.set(eventName, []);
8 }
9
10 this.events.get(eventName).push(listener);
11};
12
13EventEmitter.prototype.off = function (eventName, listener) {
14 var listeners = this.events.get(eventName) || [];
15 this.events.set(eventName, listeners.filter(function (candidate) {
16 return candidate !== listener && candidate._original !== listener;
17 }));
18};
19
20EventEmitter.prototype.once = function (eventName, listener) {
21 var emitter = this;
22
23 function wrapper() {
24 emitter.off(eventName, wrapper);
25 listener.apply(null, arguments);
26 }
27
28 wrapper._original = listener;
29 this.on(eventName, wrapper);
30};
31
32EventEmitter.prototype.emit = function (eventName) {
33 var listeners = this.events.get(eventName) || [];
34 var args = Array.prototype.slice.call(arguments, 1);
35 listeners.slice().forEach(function (listener) {
36 listener.apply(null, args);
37 });
38};
39
40var bus = new EventEmitter();
41function logUser(name) {
42 console.log('user:' + name);
43}
44bus.on('login', logUser);
45bus.once('login', function (name) {
46 console.log('once:' + name);
47});
48bus.emit('login', 'Ada');
49bus.emit('login', 'Grace');

Coding Exercises

Implement EventEmitter

Medium

Implement an EventEmitter with these methods:

  • on(eventName, listener) registers a listener and returns an unsubscribe function.
  • off(eventName, listener) removes that listener. It should also remove a once wrapper when given the original function.
  • once(eventName, listener) registers a listener that runs at most once.
  • emit(eventName, ...args) calls listeners in registration order and returns whether any listener ran.
  • listenerCount(eventName) returns the number of active listeners.

Constraints:

  • Do not use Node's events module.
  • Mutating listeners during emit should not corrupt the current emission.

Interview Questions

1Why copy the listeners array before emitting?

Listeners can call off or once wrappers can remove themselves during emission. Iterating a copied snapshot keeps the current emission deterministic and prevents index-shifting bugs.

Asked at:StripeMicrosoft
2What should `on` return?

Returning an unsubscribe function is ergonomic and avoids forcing callers to keep event names and listener references near cleanup code. It is especially useful in UI lifecycles.

Quiz

1. How can `off(event, originalListener)` remove a listener registered with `once`?

Summary

  • An EventEmitter maps event names to listener arrays.
  • `on` should return an unsubscribe function.
  • `once` is a self-removing wrapper.
  • `emit` should iterate a snapshot to avoid mutation bugs.

Cheat Sheet

EventEmitter checklist

  • Data: Map<eventName, listeners[]>.
  • on: validate function, push, return unsubscribe.
  • off: filter by listener or wrapper original.
  • once: wrapper removes itself, then calls original.
  • emit: copy listeners, call in order, return boolean.
  • Edge case: listener mutation during emit.