Behavioral

Observer

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

Beginner Pattern #19 of 23

What is the Observer Pattern?

Observer establishes a subscription mechanism. A Subject (also called Publisher/Observable) maintains a list of Observer objects and notifies them automatically whenever its state changes. Observers subscribe and unsubscribe at runtime — completely decoupled from the Subject.

This is the foundation of event-driven programming, reactive systems, and the publish-subscribe model.

Real-world analogy: A newspaper subscription. The newspaper (Subject) doesn't know who its readers are specifically — it just publishes. Subscribers (Observers) receive every new edition. Anyone can subscribe or unsubscribe without the newspaper changing how it operates.

The Problem It Solves

You have a StockMarket object. Multiple UI panels, alert systems, and logging services need to update whenever stock prices change. Hardcoding all these dependencies in StockMarket tightly couples it to every consumer — adding a new consumer requires modifying the source.

Observer lets all consumers subscribe to the StockMarket. It just calls notifyObservers() on change — no knowledge of who is listening or how many.

Participants

Subject (Publisher)
Maintains a list of observers. Provides subscribe(), unsubscribe(), and notifyObservers(). Calls notify whenever state changes.
Observer (interface)
Declares the update() method that the Subject calls. All concrete observers implement this.
ConcreteSubject
Stores the actual state. Calls notifyObservers() whenever state changes. May pass state as a parameter or observers pull it via a getter.
ConcreteObserver
Implements update() to react to Subject changes. May hold a reference to the Subject to pull additional state.

Visual Flow Diagram

Subject observers: List subscribe(obs) unsubscribe(obs) notifyObservers() «interface» Observer update(event) UIPanel update() → repaint AlertSvc update() → alert Logger notifies all observers

Java Code Example

Java — Stock Market with Push & Pull Models
// Observer interface
public interface Observer {
    void update(String symbol, double price); // PUSH model — data sent with notification
}

// Subject interface
public interface Subject {
    void subscribe(Observer o);
    void unsubscribe(Observer o);
    void notifyObservers();
}

// ConcreteSubject — Stock Market
public class StockMarket implements Subject {
    private final List<Observer> observers = new CopyOnWriteArrayList<>(); // thread-safe
    private String symbol;
    private double price;

    public void subscribe(Observer o)   { observers.add(o); }
    public void unsubscribe(Observer o) { observers.remove(o); }

    public void notifyObservers() {
        observers.forEach(o -> o.update(symbol, price)); // push data to all
    }

    public void setPrice(String symbol, double price) {
        this.symbol = symbol;
        this.price  = price;
        notifyObservers(); // auto-notify on state change
    }

    // PULL model support — observers can also query state
    public double getPrice()  { return price; }
    public String getSymbol() { return symbol; }
}

// Concrete Observers
public class StockDisplay implements Observer {
    private final String name;
    public StockDisplay(String name) { this.name = name; }

    public void update(String symbol, double price) {
        System.out.printf("[%s] %s = $%.2f%n", name, symbol, price);
    }
}

public class PriceAlert implements Observer {
    private final double threshold;
    public PriceAlert(double threshold) { this.threshold = threshold; }

    public void update(String symbol, double price) {
        if (price > threshold)
            System.out.printf("🔔 ALERT: %s hit $%.2f (threshold $%.2f)%n",
                symbol, price, threshold);
    }
}

// Client
public class Main {
    public static void main(String[] args) {
        StockMarket market = new StockMarket();

        Observer panel1 = new StockDisplay("TraderPanel");
        Observer panel2 = new StockDisplay("MobileApp");
        Observer alert  = new PriceAlert(150.0);

        market.subscribe(panel1);
        market.subscribe(panel2);
        market.subscribe(alert);

        market.setPrice("AAPL", 145.50); // all 3 notified
        market.setPrice("AAPL", 152.00); // panel1, panel2, and alert fire

        market.unsubscribe(panel2);
        market.setPrice("AAPL", 148.00); // only panel1 and alert
    }
}

When to Use / Avoid

✓ Use When

  • A change in one object requires changing others, and you don't know how many
  • Objects should be able to notify other objects without assumptions about who those objects are
  • Event-driven UI updates, reactive data pipelines, real-time dashboards
  • Model-View separation — model notifies views on data change

✕ Avoid When

  • Notification order matters and must be guaranteed — Observer order is not defined
  • Observers are expensive and updates are very frequent — notification overhead accumulates
  • Cascading updates create complex chains that are hard to trace

Real-World Examples

Pros & Cons

Pros

  • Open/Closed — add new observers without changing subject
  • Loose coupling — subject and observers know only the Observer interface
  • Supports broadcast communication — one notification to many
  • Dynamic subscription — observers can join/leave at runtime

Cons

  • Memory leaks — forgotten subscriptions keep observers alive (lapsed listener problem)
  • Unexpected updates — cascading notifications can cause hard-to-trace bugs
  • No guaranteed notification order
  • Exception in one observer can break notification of subsequent observers

How Observer Can Be Broken

⚠ Attack Vectors

  • Lapsed listener / memory leak: Observer subscribes but never unsubscribes — subject holds a strong reference forever, preventing garbage collection even after the observer is logically dead
  • Exception in one observer breaks chain: Observer A throws an exception in update() — the remaining observers B, C, D never receive the notification
  • ConcurrentModificationException: An observer subscribes or unsubscribes itself during the notifyObservers() loop — modifying the list while iterating it
  • Infinite cascade: Observer A's update() modifies the Subject's state → Subject fires another notification → triggers A again → infinite loop
  • Notification with stale data: Subject batches changes but notifies only once — observer reads old data from a getter rather than using the pushed event data

✓ Prevention

  • Always unsubscribe: Pair every subscribe() with an unsubscribe() in a teardown/destroy lifecycle method. Consider weak references for auto-cleanup
  • Wrap each observer notification in try-catch: Iterate observers in a try-catch per observer — one failing observer should not prevent others from receiving the notification
  • Use CopyOnWriteArrayList: Thread-safe and safe for modification during iteration — any subscribe/unsubscribe during notification operates on a new copy, not the active iteration
  • Re-entrancy guard: Use a flag to prevent the Subject from notifying while already in the middle of a notification cycle
  • Push model over pull model: Pass all required data in update(event) directly — avoids observers reading stale state via getters after notification
Java — Break & Fix
// ❌ BREAKING — exception in one observer breaks chain
public void notifyObservers() {
    for (Observer o : observers) {
        o.update(symbol, price); // ❌ if observer[0] throws, observer[1..n] never notified
    }
}

// ✅ FIX — isolated try-catch per observer
public void notifyObservers() {
    for (Observer o : observers) {
        try {
            o.update(symbol, price); // ✅ one failure doesn't break others
        } catch (Exception e) {
            System.err.println("Observer failed: " + e.getMessage());
        }
    }
}

// ❌ BREAKING — ConcurrentModificationException
private List<Observer> observers = new ArrayList<>();

public void update(String s, double p) {
    subject.unsubscribe(this); // ❌ modifies ArrayList during forEach iteration
}

// ✅ FIX — CopyOnWriteArrayList is safe to modify during iteration
private final List<Observer> observers = new CopyOnWriteArrayList<>();

// ❌ BREAKING — memory leak (lapsed listener)
public class MyPanel implements Observer {
    public MyPanel() {
        market.subscribe(this); // subscribes...
        // ...but never unsubscribes on close → market holds reference → GC can't collect
    }
}

// ✅ FIX — deregister in lifecycle cleanup
public void onClose() {
    market.unsubscribe(this); // ✅ always clean up
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: One-to-many dependency. Subject maintains a subscriber list. On state change, it calls update() on all subscribers. Observers subscribe/unsubscribe at runtime — completely decoupled from Subject.
  2. Push vs Pull: Push = Subject sends data in update(data) — preferred. Pull = Subject sends only notification, observer calls subject.getData() — risks reading stale state.
  3. Critical pitfalls: Always unsubscribe to prevent memory leaks (lapsed listener). Use CopyOnWriteArrayList for thread safety. Wrap each update() in try-catch so one bad observer doesn't break others.