Table of contents
Open Table of contents
Problem Statement
Implement an EventEmitter (observer pattern) that supports:
on(event, callback)— subscribe to an eventoff(event, callback)— unsubscribe from an eventemit(event, ...args)— trigger all callbacks for an eventonce(event, callback)— subscribe for a single invocation
Concepts
The observer pattern defines a one-to-many dependency: when one object (the subject) changes state, all dependents (observers) are notified. The event emitter is its most common implementation.
TypeScript: Uses generics to type event maps. See TypeScript Core Concepts.
PHP: SPL provides
SplSubjectandSplObserverinterfaces. See PHP Core Concepts.
Boilerplate
TypeScript
// event-emitter.ts
type Listener = (...args: any[]) => void;
class EventEmitter {
private events: Map<string, Listener[]> = new Map();
on(event: string, callback: Listener): void { /* TODO */ }
off(event: string, callback: Listener): void { /* TODO */ }
emit(event: string, ...args: any[]): void { /* TODO */ }
once(event: string, callback: Listener): void { /* TODO */ }
}
Hints
Hint 1: on
Get or create the listener array for this event, then push the callback.
Hint 2: once
Wrap the callback in a function that calls off after the first invocation, then register the wrapper with on.
Solution
TypeScript
type Listener = (...args: any[]) => void;
class EventEmitter {
private events: Map<string, Listener[]> = new Map();
on(event: string, callback: Listener): void {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event)!.push(callback);
}
off(event: string, callback: Listener): void {
const listeners = this.events.get(event);
if (!listeners) return;
const index = listeners.indexOf(callback);
if (index !== -1) {
listeners.splice(index, 1);
}
}
emit(event: string, ...args: any[]): void {
const listeners = this.events.get(event);
if (!listeners) return;
for (const listener of [...listeners]) {
listener(...args);
}
}
once(event: string, callback: Listener): void {
const wrapper: Listener = (...args) => {
this.off(event, wrapper);
callback(...args);
};
this.on(event, wrapper);
}
}
Python
from collections import defaultdict
from typing import Any, Callable
Listener = Callable[..., Any]
class EventEmitter:
def __init__(self) -> None:
self._events: defaultdict[str, list[Listener]] = defaultdict(list)
def on(self, event: str, callback: Listener) -> None:
self._events[event].append(callback)
def off(self, event: str, callback: Listener) -> None:
listeners = self._events[event]
if callback in listeners:
listeners.remove(callback)
def emit(self, event: str, *args: Any) -> None:
for listener in list(self._events[event]):
listener(*args)
def once(self, event: str, callback: Listener) -> None:
def wrapper(*args: Any) -> None:
self.off(event, wrapper)
callback(*args)
self.on(event, wrapper)
PHP
class EventEmitter {
/** @var array<string, array<callable>> */
private array $events = [];
public function on(string $event, callable $callback): void {
$this->events[$event][] = $callback;
}
public function off(string $event, callable $callback): void {
if (!isset($this->events[$event])) return;
$this->events[$event] = array_values(
array_filter(
$this->events[$event],
fn($listener) => $listener !== $callback
)
);
}
public function emit(string $event, mixed ...$args): void {
foreach ($this->events[$event] ?? [] as $listener) {
$listener(...$args);
}
}
public function once(string $event, callable $callback): void {
$wrapper = null;
$wrapper = function (mixed ...$args) use ($event, $callback, &$wrapper): void {
$this->off($event, $wrapper);
$callback(...$args);
};
$this->on($event, $wrapper);
}
}
Complexity Analysis
| Operation | Time | Space |
|---|---|---|
| On | O(1) | O(1) |
| Off | O(n) | O(1) |
| Emit | O(n) | O(n) |
| Once | O(1) | O(1) |
Where n = number of listeners for that event. The emit operation creates a copy of the listeners array to safely handle off calls during emission.
Cross-Language Notes
Node.js: The built-in
EventEmitterclass follows this exact pattern. It’s the foundation of Node’s event-driven architecture.
PHP: SPL provides
SplSubject/SplObserverinterfaces for a more formal observer pattern withattach(),detach(), andnotify()methods. See PHP 8.x Updates for how attributes and enums can enhance this pattern.
Python: The
asynciomodule extends this pattern with async event loops. For typed events, consider theblinkerlibrary.
C#: C# has first-class language support for the observer pattern through
eventanddelegatekeywords, making it arguably the most elegant implementation.