Hiprup

How do you implement a simple event emitter?

An event emitter lets objects publish named events and others subscribe to them.

  • on(event, listener) — register a listener under an event name.

  • emit(event, ...args) — call all listeners for that event with the data.

  • off(event, listener) — remove a listener.

Structure: store listeners in a map of event → array of functions; this powers Node's EventEmitter.

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, listener) {
    (this.events[event] ??= []).push(listener);
    return () => this.off(event, listener); // Unsubscribe
  }

  off(event, listener) {
    this.events[event] = (this.events[event] || []).filter(l => l !== listener);
  }

  emit(event, ...args) {
    (this.events[event] || []).forEach(listener => listener(...args));
  }

  once(event, listener) {
    const wrapper = (...args) => {
      listener(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

// Usage
const emitter = new EventEmitter();
const unsub = emitter.on('data', (msg) => console.log('Received:', msg));
emitter.emit('data', 'hello'); // 'Received: hello'
unsub(); // Unsubscribe
emitter.emit('data', 'world'); // Nothing — unsubscribed

emitter.once('connect', () => console.log('Connected'));
emitter.emit('connect'); // 'Connected'
emitter.emit('connect'); // Nothing — once removed itself

events stores arrays of listeners per event name. on adds a listener and returns an unsubscribe function. off removes a specific listener. emit calls all listeners for an event. once wraps a listener that auto-removes after first call. ??= lazily initializes the array.

Implement on, off, emit, and once — this is a top-5 JS interview coding question. The unsubscribe return from on() is a clean pattern. once wraps with auto-removal. ??= for lazy initialization shows modern JS.

How do you implement a simple event emitter? | Hiprup