124Implement a memory-efficient event bus with lifecycle cleanup and leak detection
Problem
A global event bus in a long-running single-page app is a classic source of memory leaks: components subscribe on mount, forget to unsubscribe, and every visit to a page adds another listener that keeps the old component alive.
Implement createEventBus({ maxListeners = 10, onLeak = () => {}, onError = (error) => console.error(error) } = {}) returning:
on(event, handler, { signal, owner, once } = {}): subscribe and return anunsubscribefunction (safe to call twice).- With
signal(anAbortSignal), unsubscribe automatically when it aborts; an already-aborted signal never subscribes. - With
owner(any object, for example a component instance), the subscription is grouped under that owner. - With
once, unsubscribe after the first call.
- With
releaseOwner(owner): remove every subscription of that owner.emit(event, payload): call the handlers in subscription order. A handler that throws must not stop the others; report the error withonError(error, event). Return the number of handlers called.listenerCount(event)andevents()(events that currently have listeners; empty ones must not linger).- Leak detection: when an event's listener count goes above
maxListeners, callonLeak(event, count)once. Warn again only after the count has dropped back to the limit or below and then exceeded it again.
Owners are kept in a WeakMap, so the bus never keeps an owner object alive.
Try it yourself
Write your solution, run it, then submit to check it against the tests.
Press Run or ⌘ Enter to execute your code.
Step-by-step
{ event, handler, once, active }. Store them in a Map from event name to a Set of subscriptions. A Set keeps insertion order and makes removal O(1).
Unlock the full solution
What's inside
- Step-by-step
- Approach
- Solution
- Complexity
- Edge cases
- Follow-up questions
- Tests
Includes 2 code listings, 5 edge cases, 4 follow-up questions.
One-time payment by UPI, card or net banking. Launch price.