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.