Type-safe event emitter pattern in Typescript - Time & Space Complexity
We want to understand how the time it takes to run a type-safe event emitter grows as we add more events or listeners.
Specifically, how does the number of listeners affect the work done when an event is emitted?
Analyze the time complexity of the following code snippet.
interface Events {
message: (text: string) => void;
error: (code: number) => void;
}
class EventEmitter {
private listeners: { [K in keyof Events]?: Events[K][] } = {};
on(event: K, listener: Events[K]) {
(this.listeners[event] ??= []).push(listener);
}
emit(event: K, ...args: Parameters) {
(this.listeners[event] ?? []).forEach(listener => listener(...args));
}
}
This code defines a type-safe event emitter that stores listeners and calls them when an event is emitted.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
forEachloop that calls each listener when an event is emitted. - How many times: It runs once for each listener registered for that event.
When you emit an event, the time to run grows with the number of listeners for that event.
| Input Size (listeners) | Approx. Operations (listener calls) |
|---|---|
| 10 | 10 |
| 100 | 100 |
| 1000 | 1000 |
Pattern observation: The work grows directly with the number of listeners. More listeners mean more calls.
Time Complexity: O(n)
This means the time to emit an event grows linearly with the number of listeners for that event.
[X] Wrong: "Emitting an event always takes the same time, no matter how many listeners there are."
[OK] Correct: Each listener must be called, so more listeners mean more work and more time.
Understanding how event emitters scale helps you write efficient code and explain your reasoning clearly in interviews.
"What if we changed the emitter to call only the first listener instead of all? How would the time complexity change?"