Skip to the solution
MediumDSA8 min read

Implement a robust Event Emitter class.

1.1k views

The problem

Question presented to candidate: "Implement an EventEmitter class from scratch, without using Node's built-in events module β€” on, once, off, and emit at minimum. What happens if one listener throws an exception during emit β€” should that stop the remaining listeners from running? What happens if a listener adds or removes another listener for the SAME event while emit is in the middle of running?"

What a strong answer should cover:

  • Internal storage is a map from event name to an array (or similar ordered structure) of registered listener entries, each tracking the callback function and whether it is a "once" listener.
  • on(event, fn) appends a listener; once(event, fn) appends one that auto-removes itself after firing exactly one time; off(event, fn) removes a specific listener (or all listeners for an event if no function is given).
  • emit(event, ...args) must snapshot the current listener array BEFORE iterating over it β€” listeners added or removed DURING an emit call must not affect which listeners THAT SPECIFIC emit call notifies.
  • A listener throwing should not be allowed to silently prevent SIBLING listeners for the same event from running β€” a robust emitter isolates each listener call, typically with a try/catch per listener, and reports the error somewhere rather than letting it propagate and abort the loop.
  • Node's own real, built-in EventEmitter does NOT do this β€” a listener that throws genuinely stops iteration and propagates synchronously out of emit(), which sibling listeners registered after the throwing one never see. This is worth citing explicitly as a deliberate design difference from a "robust" version, not an oversight.
  • An emit("error", ...) call with zero registered listeners conventionally throws the error synchronously rather than silently discarding it β€” this specific convention exists in Node's real EventEmitter and is worth replicating, since silently swallowing unhandled errors is a common real-world source of silently-failing systems.

Clarifying questions expected:

  • "Should listener errors be caught and reported, matching Node's newer safety-oriented patterns, or should they propagate and stop the emit loop, matching Node's actual legacy behavior?" β€” a genuine, real design fork worth naming explicitly rather than assuming one answer.
  • "Does emit() need to support async listeners specifically, i.e. should it await returned promises, or is fire-and-forget acceptable?" β€” clarifies whether emit() itself needs to be async-aware at all.

Code / implementation expected: Yes β€” a full, runnable EventEmitter class plus a real test suite proving listener-error isolation, pre-iteration snapshotting, and a genuine contrast against Node's real built-in EventEmitter.

  • design-patterns
  • pubsub
  • event-emitter

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 7 sections

Run the code

Edit the code and run it here. Each console line with an expected value becomes a test.

JavaScriptA from-scratch EventEmitter with pre-iteration snapshotting and per-listener error isolation, run through 7 real scenarios (run directly)
JavaScriptReference: the same throw scenario run against Node's REAL built-in node:events EventEmitter, to prove the contrast this doc cites (illustrative only, not part of the browser example)
import { EventEmitter as NodeEmitter } from "node:events";
const ne = new NodeEmitter();
const log = [];
ne.on("boom", () => log.push("before-throw"));
ne.on("boom", () => { throw new Error("native listener blew up"); });
ne.on("boom", () => log.push("after-throw (does this run?)"));
try {
  ne.emit("boom");
} catch (e) {
  console.log("emit() itself threw synchronously:", e.message);
}
console.log("log after native emit:", log);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 54 of 165 in JavaScript

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

What does Object.create do?

Continue