1 min read·Run it here

Design an Event Emitter

The small event bus drill: subscribe handlers, emit in order, unsubscribe by function identity, then add once with a snapshot-safe loop.

Pattern
Map event -> ordered handlers
Time
O(listeners)
Space
O(listeners)

An event emitter is one of the cleanest reps for this, method APIs, callback identity, and mutation while iterating.

The recipe

Say before you type: "Map event names to ordered handlers. emit copies the list, then calls each handler."

Start with:

private handlers = new Map<string, Set<Handler>>();

A Set keeps insertion order and prevents duplicate handler references.

off

off(event, handler) depends on function identity. This works:

const handler = () => {};
emitter.on("save", handler);
emitter.off("save", handler);

This does not:

emitter.off("save", () => {});

That second arrow is a different function.

once

once() wraps the original handler:

const wrapper = (payload) => {
  off(event, wrapper);
  handler(payload);
};

Register the wrapper through on, so once and on handlers share one list and stay in subscription order — a once registered first still runs first. A separate once-list is the tempting shortcut and it silently reorders every emit.

Make emit() iterate over [...handlers] so removing during emit does not corrupt the loop.