Skip to solution
mediumDSA

Implement a robust Event Emitter class.

1.1k views
01

Understand 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-patternspubsubevent-emitter
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for JavaScript design-pattern / low-level API implementation interview questions. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Every behavior shown below is **real, captured

Solution ready — 2 min read

Classified // press E to declassify

04

Run the code

JSA from-scratch EventEmitter with pre-iteration snapshotting and per-listener error isolation, run through 7 real scenarios (run directly)
Reference: 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);
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 54 of 165 decoded in the JavaScript track. One more won't hurt.

Back to track