DEV Community

Cover image for Why Pub/Sub Is the Architecture Trick Every Developer Should Know
Anupam Thakur
Anupam Thakur

Posted on

Why Pub/Sub Is the Architecture Trick Every Developer Should Know

Did you ever run into a situation where an event or specific action happens in your code, and you want other components to react — maybe update the UI, trigger a toast, refresh some data — but you end up creating a mess of callback functions just to make things talk to each other? One function calling another, that one calling a third, and suddenly your code looks like a bowl of tightly coupled spaghetti🍝.

Example:

Component A fires an event, so you directly call a function in Component B. Then B needs to inform C, so you pass another callback. And eventually C needs data from A, so you pass props up and callbacks down. It works… but it becomes fragile, painful to maintain, and every new requirement feels like refactoring your entire app.

Now compare that chaos with using Pub/Sub:

Component A simply publishes an event like user:login, and whoever cares — Component B, C, or D — just subscribes to it. No direct dependencies, no tangled wiring, no callback chains. A doesn’t know who’s listening, and listeners don’t care who triggered it.

// Component A
pubsub.publish("user:login", user);

// Component B
pubsub.subscribe("user:login", (user) => updateHeader(user));

// Component C
pubsub.subscribe("user:login", (user) => refreshDashboard(user));

// Component D
pubsub.subscribe("user:login", () => showToast("Welcome back!"));

Enter fullscreen mode Exit fullscreen mode

Your code suddenly becomes modular, clean, and beautifully predictable. Ok , now lets dive deep into what is and how to do pub/sub.

Publish-Subscribe ( pub/sub )

Pub/sub is a simple messaging pattern where one part of your code announces that something happened, and other parts of your code listen for those announcements.

  • The part that announces is called the publisher.

  • The parts that listen are called the subscribers.

They never talk to each other directly.
They only communicate through a middle system (the “event channel”).

It’s like saying:

“Hey, event happened!”
and whoever cares will react — without the publisher knowing who they
are.

Super simple example:

Imagine a Youtube channel

  • You publish a video
  • Whoever subscribed the channel get automatically notified.
  • You don't have to message each subcriber.

subscribers gets notification of new video uploaded

🧩 Let’s Build a Simple Pub/Sub System from Scratch

To understand Pub/Sub, imagine breaking the system into three simple parts — each with a very clear job :

1. The Publisher (🔊The speaker)

This part of your code creates an event. And publish events with optional data. Its doesn't know about :

  • who will listen
  • how many listners are
  • and what will it do It just says

Hey! an event happened, i am publishing it.

 publish(eventName, data) {
    if (!this.events[eventName]) return;
    this.events[eventName].forEach(callback => callback(data));
  }
Enter fullscreen mode Exit fullscreen mode

2. The Subscriber (👂The Listener)

This part of your code subscribe the Listeners and responsible to receive messages for subscribed events.

And subscribes dont know:

  • who trigger the events
  • why the event happens They only care when it happens.

This is a method which returns a function which is used to unsubscribe from event works as a clean-up function.

subcribe(eventName, callback) { 

 if(!events[eventName]) events[eventName] = [];

 events[eventName].push(callback);

 return ()=>{events[eventName].filter(l => l !== callback)};

}
Enter fullscreen mode Exit fullscreen mode

3. Event Channel (🙎‍♂️The Middleman)

This is the hub where everything connects.

  • stores all events and thier listeners
  • links publishers and subscribers
  • decides which subscribers to notify when an event occurs
// stores event and listeners in Set
// like
 events { 
          "event-name" : [listener1,listener2, ...],
          "event-name1": [listener1,listener2, ...]
        }
Enter fullscreen mode Exit fullscreen mode

💻 Full code example

Below is the full code that we used to explain the concepts above;

const pubsub = {
  events: {},

  subscribe(eventName, callback) {
    if (!this.events[eventName]) this.events[eventName] = [];

    this.events[eventName].push(callback);

    return ()=>{events[eventName].filter(l => l !== callback)};
  },

  publish(eventName, data) {
    if (!this.events[eventName]) return;
    this.events[eventName].forEach(callback => callback(data));
  }
};
Enter fullscreen mode Exit fullscreen mode

🛠️ How to use

1️⃣ Subscribe to an event

const unsubscribe = pubsub.subscribe('task:completed',(task) => {
   console.log("A task was completed:",task);
 })

// call unsubscribe function
// when you doesn't want to get notify
Enter fullscreen mode Exit fullscreen mode

2️⃣ Publish an event

pubsub.publish("task:completed",{id:1,title:"Learn Pub/Sub"});
Enter fullscreen mode Exit fullscreen mode

When publish runs, the subscriber automatically gets triggered

✅ Done

Congrats, now your code ready to yell to listners that the event is happen whenever publish runs.

// output  
Event received: task:completed
Payload: { id: 1, title: "Learn Pub/Sub" }
Enter fullscreen mode Exit fullscreen mode

Clean, readable, maintainable and scalable instead callbacks hell. It’s a practical weapon for cleaner, more flexible code — especially in interactive apps, real-time features, and systems that grow over time.

🧠 How I Discovered Pub/Sub While Fighting My Own Code Chaos

The first i know about Pub/Sub and used in my code, when i was working on my project Pomodoro Timer. Where i need to keep the track of remaining time to update UI, and switch between modes (focus, short-break and long-break) on every timer hits zero.

EventEmitter as pub/sub system

class EventEmitter {
    private _events:Record<string, Listener[]>={};

    on(eventName:string,callback:Listener){
        if(!this._events[eventName]) this._events[eventName] = [];
        this._events[eventName].push(callback);

        return ()=>{this._events[eventName].filter(l=> l !== callback)}
    }

    emit(eventName:string, ...args:any[]){
        if(!this._events[eventName]) return;
        this._events[eventName].forEach((l)=> l(...args));
    }
}

Enter fullscreen mode Exit fullscreen mode

Pomodoro core structure


export class Pomodoro {

  private _emitter = new EventEmitter();

  subscribe(eventName:string,callback: Listener): Unsubscribe {
   return this._emitter.on(eventName,callback);
  }

start(): void {
    this._interval = setInterval(() => this.tick(), 1000);
    this._isTicking = true;
    // publisher emiting timer status on every start method call
    this._emitter.emit('status',this.isTicking);
  }

  private tick(): void {
    if (this._second === 0) {
      if (this._minute === 0) {
        this._isTicking = false;
        this.tryCallback();
        return;
      }
      this._minute -= 1;
      this._second = 59;
    } else {
      this._second -= 1;
    }

// another publisher
// publish tick event and data on every second

    this._emitter.emit('tick',{min:this.minute,sec:this.second});
  }

}
Enter fullscreen mode Exit fullscreen mode

And that’s pub/sub helped me, Now i can add as many as subscribers without touching the core code structure just need to call subcribe method and booyah!!! its done and unsubscribe the event when i don't need.

🌟Benefits of Using Pub/Sub

  • Decoupled code : components don’t know (or care) about each other; they just react to events.
  • Cleaner structure : no callback hell, no prop drilling, no tangled chains.
  • Scales easily : add new behaviors without touching existing code.
  • Reusable events : one event can trigger actions in multiple places.
  • Readable flow : publishers feel like actions, subscribers feel like reactions.
  • Central control : log, throttle, filter, or queue events from one spot.
  • Easier testing : test events without mocking entire components.

At the end, Pub/Sub isn’t just a pattern — it’s a mindset. When your app grows, you’ll quickly realize that tightly connecting everything together only traps you later. Pub/Sub gives you the freedom to build features without breaking old ones, experiment without fear, and write code that feels calm instead of chaotic.

If you’re just starting out, mastering this pattern early will save you countless headaches down the road. Keep your components independent, let events do the talking, and your future self will ✨thank you for choosing the cleaner path.

🙋‍♂️👋 Thanks for Reading

Alright, wrapping this up — this is something I learned the hard way, and I felt it’s worth sharing so other developers can discover this pattern earlier than I did. Keep learning, keep exploring, keep growing… and yes, work smart so you can land that dream job (because let’s be honest — passion is great, but the paycheck💰 definitely helps).

Thanks for reading! I’ll see you in the next blog. If you’ve got questions or thoughts, drop them in the comments. And if this helped you even a little, don’t forget to like and share it with your fellow developers.🙌

Top comments (0)