SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignObserver Pattern
Software DesignBehavioral Patterns

Observer Pattern

Define a one-to-many dependency so that when one object changes state, all its dependents are notified automatically.

January 20, 20255 min read
design-patternsjavaobserverbehavioralpub-sub

One-line Definition

Establishes a subscription mechanism to notify multiple dependent objects automatically when a publisher's state changes.


Problem

Suppose you are building a weather app. You have a central WeatherData object and multiple displays (Current Conditions, Statistics, Forecast). If the displays constantly poll the WeatherData object to ask "Has the temperature changed?", you waste immense CPU cycles and network bandwidth. If you hardcode the update calls inside WeatherData to explicitly notify each display, you violate the Open/Closed Principle every time you add a new display. The Observer Pattern solves this by introducing a publish-subscribe mechanism, allowing objects to dynamically listen for events.


Real-world Analogy

Think of a YouTube channel. You don't visit the channel every 5 minutes to see if a new video is uploaded. Instead, you click the "Subscribe" button. When the creator (the publisher) uploads a video, YouTube automatically sends a notification to you and every other subscriber (the observers). You can unsubscribe at any time.


Structure

  • Client
    • Instantiates the Publisher and Observers, then registers the Observers with the Publisher.
  • Publisher (Subject)
    • Maintains a list of subscribers and sends notifications to them when its state changes.
  • Subscriber (Observer)
    • Interface declaring an update() method that gets called by the Publisher.
  • ConcreteSubscriber
    • Implements the update() method to react to the state change.

Diagram

classDiagram
    class Client {
    }
    class Publisher {
        -List~Subscriber~ subscribers
        +subscribe(s: Subscriber)
        +unsubscribe(s: Subscriber)
        +notifySubscribers()
    }
    class Subscriber {
        <<interface>>
        +update(data)
    }
    class ConcreteSubscriberA {
        +update(data)
    }
    class ConcreteSubscriberB {
        +update(data)
    }
    
    Client --> Publisher
    Client --> ConcreteSubscriberA
    Publisher o-- Subscriber : notifies
    ConcreteSubscriberA ..|> Subscriber
    ConcreteSubscriberB ..|> Subscriber

Code Walkthrough

Notice how the WeatherStation (Publisher) doesn't know anything about PhoneDisplay or WindowDisplay. It just iterates over a list of abstract Subscriber interfaces and calls update().

interface Subscriber {
    void update(float temperature, float humidity);
}

class WeatherStation {
    private final List<Subscriber> subscribers = new ArrayList<>();
    private float temperature;
    private float humidity;

    public void subscribe(Subscriber s) {
        subscribers.add(s);
    }

    public void unsubscribe(Subscriber s) {
        subscribers.remove(s);
    }

    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        notifySubscribers();
    }

    private void notifySubscribers() {
        for (Subscriber s : subscribers) {
            s.update(temperature, humidity);
        }
    }
}

class PhoneDisplay implements Subscriber {
    @Override
    public void update(float temperature, float humidity) {
        System.out.println("📱 Phone Alert: Temp " + temperature + "°C, Humidity " + humidity + "%");
    }
}

class WindowDisplay implements Subscriber {
    @Override
    public void update(float temperature, float humidity) {
        System.out.println("🪟 Window Display: It is currently " + temperature + " degrees.");
    }
}

class Main {
    public static void main(String[] args) {
        WeatherStation station = new WeatherStation();

        Subscriber phone = new PhoneDisplay();
        Subscriber window = new WindowDisplay();

        station.subscribe(phone);
        station.subscribe(window);

        // State changes — automatically pushed to all subscribers
        station.setMeasurements(25.5f, 60.0f);
        System.out.println("---");
        
        station.unsubscribe(window);
        station.setMeasurements(26.0f, 65.0f);
    }
}

Bad vs Good

Bad Approach

Problems

  • Constant polling wastes resources.
  • Hardcoding the updates heavily couples the Publisher to the Concrete Observers.
  • Cannot dynamically add or remove displays at runtime.
class WeatherStation {
    private PhoneDisplay phone;
    private WindowDisplay window;

    public void setMeasurements(float temp, float humidity) {
        // Tightly coupled! Breaking Open/Closed Principle
        phone.update(temp, humidity);
        window.update(temp, humidity);
    }
}

Better Approach

Improvements

  • The Publisher is strictly decoupled from the concrete Observers.
  • Subscribers can dynamically register and deregister at runtime.
  • Polling is entirely eliminated via push notifications.
// Publisher only knows about the Subscriber interface
public void notifySubscribers() {
    for (Subscriber s : subscribers) {
        s.update(temperature, humidity);
    }
}

Pros vs Cons

ProsCons
Enables a robust publish-subscribe architectureSubscribers are notified in random/unspecified order
Adheres strictly to the Open/Closed PrincipleCan cause memory leaks if subscribers forget to unsubscribe (Lapsed Listener Problem)
Establishes relations dynamically at runtimeDebugging a massive chain of cascading updates is notoriously difficult
Completely eliminates the need for polling
Publisher and Subscribers can evolve entirely independently

When to Use

  • Changes to the state of one object must trigger actions in other objects, and the exact list of objects is unknown or changes dynamically.
  • You are building UI components that need to react to underlying data model changes (MVC architecture).
  • You want to implement an event-driven system without heavy messaging middleware.

When Not to Use

  • The objects are tightly bound and always execute sequentially in a fixed, predictable flow.
  • A central Mediator would better orchestrate complex workflow logic than scattered events.

Real-world Examples

  • java.util.Observer and java.util.Observable (Deprecated in Java 9, but historically the classic example).
  • Java 9+ java.util.concurrent.Flow API (Reactive Streams).
  • GUI Event Listeners (e.g., button.addActionListener()).
  • Data binding in modern frontend frameworks (React, Vue, Angular).

Key Takeaway

The Observer Pattern creates a dynamic publish-subscribe relationship, allowing an object to notify an open-ended list of dependents when its state changes. Use it to build highly decoupled, event-driven architectures (like UI listeners), but proactively protect against memory leaks by ensuring subscribers always deregister when they are destroyed.

PreviousAbstract Factory PatternNextVisitor Pattern