AlgoMaster Logo

Observer Design Pattern

Last Updated: December 31, 2025

Ashish

Ashish Pratap Singh

6 min read

This pattern shines in scenarios where:

  • You have multiple parts of the system that need to react to a change in one central component.
  • You want to decouple the publisher of data from the subscribers who react to it.
  • You need a dynamic, event-driven communication model without hardcoding who is listening to whom.

A straightforward approach is to directly call update methods on other objects whenever something changes. For example, a NewsPublisher might call update() on a MobileAppEmailService, and WebsiteFeed every time a new article is published.

But as the number of subscribers grows, this approach becomes rigid, hard to scale, and violates the Open/Closed Principle. Adding or removing subscribers requires modifying the publisher class. It also tightly couples the publisher to all its subscribers.

The Observer Pattern solves this by decoupling the subject and its observers, allowing them to interact through a common interface. Observers can be added or removed at runtime, and the subject doesn’t need to know who they are.

Let’s walk through a real-world example to see how we can apply the Observer Pattern to build a flexible, extensible, and loosely coupled notification system that’s perfect for event-driven applications.

1. The Problem: Broadcasting Fitness Data

Imagine you are building a Fitness Tracker App that connects to a wearable device. The device continuously streams real-time fitness data: steps taken, active minutes, and calories burned. This data flows into a central FitnessData object.

Smart Watch

Now, multiple modules within your app need to react to these updates:

ModuleResponsibility
LiveActivityDisplayShows real-time stats on the dashboard
ProgressLoggerPersists data to a database for trend analysis
GoalNotifierSends alerts when the user hits milestones

The Naive Approach

In a simple approach, the FitnessData object directly holds and manages references to all its dependent modules.

FitnessDataNaive

FitnessAppNaiveClient

This works initially, but let us think about what happens as the application grows.

Problems with This Approach

1. Tight Coupling

The FitnessData class now has intimate knowledge of every module that cares about its data. It holds direct references to LiveActivityDisplayProgressLogger, and NotificationService.

If any of these classes change their interface, or if you want to replace one with a different implementation, you must modify FitnessData.

2. Violates the Open/Closed Principle

What happens when you want to add a WeeklySummaryGenerator? Or a SocialSharingService that posts achievements to social media?

Each new feature requires you to:

  • Add a new field to FitnessData
  • Modify the newFitnessDataPushed() method
  • Potentially update the constructor

The class is open for modification when it should be closed.

3. Inflexible and Static Design

Modules like the NotificationService or ProgressLogger can’t be added or removed at runtime. What if the user disables notifications in their settings?

You’ll need to add even more conditionals to manually enable/disable parts of the code — making things fragile and error-prone.

4. Responsibility Bloat

The FitnessData class should have one job: managing fitness metrics. Instead, it is now responsible for UI updates, database logging, and notification logic. This violates the Single Responsibility Principle and makes the class difficult to test in isolation.

5. Scalability Bottlenecks

As the number of dependents grows, newFitnessDataPushed() becomes a lengthy sequence of method calls, each potentially with different parameters and error handling requirements. The method becomes a bottleneck that every developer must understand and modify.

What We Really Need

We need a better, scalable way to solve this problem, something that allows:

  • FitnessData to broadcast changes to multiple listeners, without knowing who they are
  • Each module to subscribe or unsubscribe dynamically
  • Loose coupling between the subject and observers
  • Each module to decide for itself how to respond to changes

This is exactly what the Observer pattern provides.

2. Understanding the Observer Pattern

The Observer Design Pattern provides a clean and flexible solution to the problem of broadcasting changes from one central object (the Subject) to many dependent objects (the Observers) — all while keeping them loosely coupled.

In our Fitness Tracker App, the Observer Pattern allows the FitnessData object to notify all registered modules (like LiveActivityDisplayProgressLogger, and GoalNotifierautomatically whenever new fitness data is received — without needing to know what those modules are or how they respond.

Class Diagram

1. Observer Interface (e.g., FitnessDataObserver)

  • Declares an update() method.
  • All modules that want to listen to fitness data changes will implement this interface.
  • Each observer defines its own logic inside update() to respond to updates.

2. Subject Interface (e.g., FitnessDataSubject)

Declares methods to:

  • registerObserver() – subscribe to updates
  • removeObserver() – unsubscribe from updates
  • notifyObservers() – notify all current observers of a change

The subject doesn't care who the observers are — it just sends updates.

3. ConcreteSubject (e.g., FitnessData)

  • Implements FitnessDataSubject.
  • Maintains an internal list of FitnessDataObserver objects.
  • When new data is pushed, it updates its internal state and calls notifyObservers() to broadcast the change.

4. ConcreteObservers (e.g., LiveActivityDisplay)

  • Implement the FitnessDataObserver interface.
  • When update() is called, each observer pulls relevant data from the subject and performs its own logic (e.g., update UI, log progress, send alerts).

3. Implementing Observer Pattern

Let’s refactor our fitness tracker system using the Observer Pattern.

1. Define the FitnessDataObserver Interface

Each observer receives a reference to the subject and can pull whatever data it needs. This keeps the interface stable even as FitnessData evolves.

2. Define the FitnessDataSubject Interface

This interface allows observers to register and unregister themselves, and lets the subject notify all observers of changes.

3. Implement the FitnessData Class (ConcreteSubject)

This class will hold the actual fitness data, manage a list of observers, and notify them upon data changes.

4. Implement Observer Modules

Each observer defines its own update() logic based on the new fitness data.

LiveActivityDisplay

ProgressLogger

GoalNotifier

5. Client Code

Now we wire everything together and simulate some fitness updates:

What We Achieved

  • Loose Coupling: FitnessData doesn’t care who is listening — it just broadcasts
  • Extensibility: Adding a new module (like WeeklySummaryGenerator) only requires implementing FitnessDataObserver — no changes to FitnessData
  • Runtime Flexibility: Observers can be added/removed dynamically (e.g., based on user settings)
  • Clean Separation of Concerns: Each module is responsible for its own behavior and logic