The EventEmitter class, on/emit/once/removeListener, custom events, why streams and HTTP are built on EventEmitter, and memory leak prevention.
Module F-9 — Event Emitters & Node's Event-Driven Core
What this module covers: The EventEmitter class is the foundation of Node.js's architecture. HTTP servers, file streams, database connections, WebSockets — they all inherit from EventEmitter. You have already used it without knowing it: every time you called
server.listen()orreq.on('data', ...), you were using an EventEmitter. This module explains how EventEmitter works, how to build your own, how to avoid the memory leak that trips up every developer, and how the pattern underpins Node.js internals. Understanding this bridges Foundation to everything in the Practitioner and Architect phases.
What Is an Event Emitter?
An EventEmitter is an object that:
- Lets you register named listeners — functions to call when a specific event occurs
- Lets you emit events by name — which synchronously calls all registered listeners
It is the observer pattern, built into the Node.js runtime.
Simple enough. The power comes from the fact that the emitter and the listener are decoupled — the code that fires the event does not know who is listening, and the listener does not know when or why the event was fired.
Core EventEmitter API
Building Your Own EventEmitter Subclass
The real pattern: extend EventEmitter in your own classes. This is how Node.js HTTP servers, streams, and virtually every I/O object are built.
Output:
The caller does not need to know anything about the pipeline's internals. It just listens for named events.
The error Event Is Special
If an EventEmitter emits an 'error' event and there is no listener registered for it, Node.js throws the error and crashes the process. This is not optional — it is a design decision to force you to handle errors explicitly.
Any class extending EventEmitter should either:
- Register a default
'error'listener in the constructor, or - Document clearly that callers must register one
The Memory Leak Warning
This is the single most common EventEmitter mistake. By default, Node.js warns if you register more than 10 listeners for the same event on the same emitter:
Why? If you accidentally register a new listener on every request without removing the old one, you have a memory leak — the listener count grows unboundedly and the listeners are never garbage collected.
The fix: remove listeners when you are done with them.
If you genuinely need many listeners (e.g. many subscribers to a shared emitter), increase the limit:
But increasing the limit should be a deliberate decision with a comment explaining why — not a way to silence the warning.
Synchronous vs Asynchronous Listeners
EventEmitter is synchronous by default. emit() calls all listeners immediately, in the order they were registered, before returning. There is no queuing or async scheduling:
This means if a listener does heavy synchronous work, it blocks the event loop — same as any other synchronous code. Keep listeners fast, or offload heavy work:
Note: if a listener is async and throws, the rejection is unhandled by default. The emitter does not await its listeners. For async event handling in production, always add try/catch inside the listener or use a wrapper.
How Node.js Uses EventEmitter Internally
You have been using EventEmitters since F-1 without realising it. Every major I/O object in Node.js extends EventEmitter:
HTTP Server:
http.createServer() returns an http.Server which extends EventEmitter. The request callback shorthand (createServer(handler)) is simply .on('request', handler).
HTTP Request (IncomingMessage):
File Streams:
Process itself:
process is itself an EventEmitter.
EventEmitter vs Callbacks vs Promises
You now have three async patterns. When to use each:
| Pattern | Best For |
|---|---|
| Callbacks | Simple one-time async operations. Legacy APIs. |
| Promises / async-await | One-time operations with a clear success/failure. Most application code. |
| EventEmitter | Multiple events over time. Streams of data. Pub/sub within a process. Multiple listeners for the same event. |
The key distinction: Promises resolve once. EventEmitters fire many times. A file read finishes once → Promise. A server receives many requests → EventEmitter. A data pipeline processes thousands of records → EventEmitter.
Practical Pattern: Internal Event Bus
A lightweight event bus for decoupling modules inside a single Node.js process:
This pattern — emit from routes, listen in separate modules — keeps business logic out of HTTP handlers and makes the system easy to extend. Adding a new action when a user is created means adding a new listener file, not modifying the route.
Summary
- EventEmitter is Node.js's observer pattern.
on()registers listeners,emit()fires them. once()for one-time listeners.off()to remove them.listenerCount()to check how many are attached.- Extend EventEmitter for your own classes that emit events over time (pipelines, connection managers, worker pools).
- Always register an
'error'listener. An unhandled'error'event crashes the process. - Memory leaks happen when you register listeners without removing them. Use
off()oronce()appropriately. - EventEmitter is synchronous. Listeners run inline when
emit()is called. Async listeners needtry/catchinside them. - Node.js internals use EventEmitter everywhere — HTTP server, IncomingMessage, streams,
processitself. - Use EventEmitters for streams of events over time. Use Promises for one-time results. Use callbacks only when working with legacy APIs.
This completes Phase 1 — Foundation. You now have the mental models, tools, and patterns to understand and build basic Node.js applications. Phase 2 takes all of this and builds it into production-quality application architecture: authentication, TypeScript, testing, security, and deployment.
Phase 1 recap: Runtime model → Module system → File I/O → Async programming → npm → Express APIs → Databases → Project structure → Event-driven core. Every concept in Phase 2 and Phase 3 builds directly on one or more of these.
What happens if an EventEmitter instance emits an 'error' event and there is no listener registered for it?
Which of the following best describes how EventEmitter executes its listeners when emit() is called?
What is the primary cause of the MaxListenersExceededWarning in Node.js applications?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion