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 itselfevents 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.