The Publish/Subscribe (Pub/Sub) pattern takes the Observer Pattern a step further by introducing a central event bus, so publishers and subscribers never need any direct reference to each other at all — the architecture behind Node.js EventEmitter and countless messaging systems.
1Publish / Subscribe Pattern | JavaScript Tutorial - In-Depth Guide Part 1
Publish/Subscribe introduces a central event bus (or 'broker') that publishers send messages to by topic/event name, and subscribers listen to by that same name — neither side ever references the other directly.
const bus = createEventBus();
bus.subscribe('user:login', (user) => console.log('Logged in:', user));
bus.publish('user:login', { name: 'Ana' }); // logs 'Logged in: {...}'Fully Decoupled Messaging
2Publish / Subscribe Pattern | JavaScript Tutorial - In-Depth Guide Part 2
A minimal event bus implementation just maps topic names to arrays of subscriber callbacks, and publishing a topic calls every subscriber registered for it.
function createEventBus() {
const topics = new Map();
return {
subscribe(topic, callback) {
if (!topics.has(topic)) topics.set(topic, []);
topics.get(topic).push(callback);
},
publish(topic, data) {
(topics.get(topic) || []).forEach((cb) => cb(data));
},
};
}A Minimal Event Bus
3Publish / Subscribe Pattern | JavaScript Tutorial - In-Depth Guide Part 3
Because publishers and subscribers never reference each other, you can add new features that react to existing events (like adding analytics tracking to a 'checkout:complete' event) without touching the code that publishes them at all.
// Existing checkout code never needs to change:
bus.publish('checkout:complete', order);
// A brand-new analytics module, added later, independently:
bus.subscribe('checkout:complete', (order) => trackPurchase(order));Adding Features Without Touching Existing Code
4Publish / Subscribe Pattern | JavaScript Tutorial - In-Depth Guide Part 4
Node.js's built-in EventEmitter class is a production-grade, widely-used implementation of exactly this pattern, supporting topic-based (event-name-based) subscription with additional conveniences like once() and error handling.
const { EventEmitter } = require('events');
const bus = new EventEmitter();
bus.on('data', (chunk) => process(chunk)); // subscribe
bus.emit('data', someChunk); // publishNode.js EventEmitter
5Publish / Subscribe Pattern | JavaScript Tutorial - In-Depth Guide Part 5
The same decoupling that makes Pub/Sub powerful also makes it easy to lose track of who's listening to what — always support and use unsubscribe, and consider naming conventions (like 'domain:action') to keep a growing set of topics organized.
// Centralize topic names to avoid typos and ease searching:
const TOPICS = {
USER_LOGIN: 'user:login',
CHECKOUT_COMPLETE: 'checkout:complete',
};
bus.subscribe(TOPICS.USER_LOGIN, handleLogin);Managing Complexity at Scale
6Step-by-Step Breakdown
Publish/Subscribe introduces a central event bus (or 'broker') that publishers send messages to by topic/event name, and subscribers listen to by that same name — neither side ever references the other directly.
Checkpoint: In Publish/Subscribe, does a publisher need a direct reference to its subscribers?
- →Yes, exactly like the classic Observer Pattern
- →No, both sides only interact through the shared event bus
A minimal event bus implementation just maps topic names to arrays of subscriber callbacks, and publishing a topic calls every subscriber registered for it.
Because publishers and subscribers never reference each other, you can add new features that react to existing events (like adding analytics tracking to a 'checkout:complete' event) without touching the code that publishes them at all.
Node.js's built-in EventEmitter class is a production-grade, widely-used implementation of exactly this pattern, supporting topic-based (event-name-based) subscription with additional conveniences like once() and error handling.
Checkpoint: Is Node.js's EventEmitter class an implementation of the Publish/Subscribe pattern?
- →Yes, .on()/.emit() correspond to subscribe/publish
- →No, it uses a completely unrelated architecture
The same decoupling that makes Pub/Sub powerful also makes it easy to lose track of who's listening to what — always support and use unsubscribe, and consider naming conventions (like 'domain:action') to keep a growing set of topics organized.
Next, we'll explore 'XSS Basics'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Use Pub/Sub to Decouple Accessibility Announcements from Business Logic
Publishing a generic 'notification:shown' event that a dedicated accessibility module subscribes to (to update an ARIA live region) keeps announcement logic centralized and consistent, without scattering live-region-update code throughout every feature that needs to show a notification.
SEO Implications
- 1
No Direct SEO Effect
Publish/Subscribe is an application-architecture pattern; SEO relevance is limited to enabling more maintainable, loosely-coupled code that reduces the risk of feature interactions breaking rendered content.
Best Practices
Centralize Topic Name Constants Instead of Scattering String Literals
Using shared constants for topic names avoids typo-based bugs (a mismatched string silently means a subscriber never fires) and makes it easy to find every publisher/subscriber for a given topic.
Always Support and Use Unsubscribe to Avoid the Same Memory Leak Risk as the Observer Pattern
A Pub/Sub bus that never releases stale subscriber references has exactly the same memory-leak risk as an Observer Pattern subject that never removes old observers.
Frequent Bugs
A typo in a topic name string (e.g. subscribing to 'user:logIn' while the publisher emits 'user:login') causes the subscriber to silently never fire, with no error raised anywhere.
Use centralized, shared constants for topic names instead of repeating string literals at every subscribe/publish call site, eliminating the possibility of a typo mismatch.
Subscribing to a topic inside a component or module that gets created repeatedly, without ever unsubscribing, causing the same memory-leak pattern as a forgotten event listener.
Store the subscription reference and explicitly unsubscribe during cleanup, exactly as you would for a DOM event listener.
Real-World Examples
Decoupling Order Processing from Notification and Analytics Modules
An e-commerce backend needed to trigger email notifications, SMS alerts, and analytics tracking whenever an order was placed, without the core order-processing code needing to know about any of those downstream concerns.
// order-service.js — knows nothing about notifications or analytics:
bus.publish('order:placed', order);
// notification-service.js — subscribes independently:
bus.subscribe('order:placed', (order) => sendConfirmationEmail(order));
// analytics-service.js — also subscribes independently:
bus.subscribe('order:placed', (order) => trackOrderPlaced(order));