Design Pattern

Observer Pattern

Clean Java-only production-ready implementation.


What Problem It Solves

When one object changes, multiple dependent objects need to be notified. You don't want the subject to know about every observer. Observer decouples the subject (publisher) from observers (subscribers).

Observer vs Pub-Sub

AspectObserverPub-Sub
CouplingSubject knows observers (via interface)Publisher and subscriber don't know each other
DeliveryDirect (in-process)Via broker (Kafka, Redis, in-memory bus)
FailureOne slow observer blocks othersAsync queue decouples
LLD fitIn-process notification, small scaleCross-service, large scale

Common Interview Traps

TrapWhyFix
Forgetting observer removalMemory leak (observer holds reference to subject)
  • Provide removeObserver
  • weak references
Blocking in observer callbackSlow observer delays all othersUse async notification (event queue)
Notification order dependencyObservers assume specific orderObservers should be order-independent
Observing the same subject twiceDouble processingCheck before adding

Observer Variants

VariantHowWhen
Pull
  • Subject notifies "something changed"
  • observers call back to get data
Observers need different data subsets
PushSubject sends full data with notificationObservers need the same data
Async (Event Bus)Uses a message queue between subject and observersDecouple in time, not just space (Kafka/Redis pub/sub)

Where Observer Appears in LLD Problems

ProblemSubjectObserversNotification Trigger
Notification Service (#18)NotificationManagerEmailHandler, SMSHandler, PushHandlerNew notification created
Live Comments (#73)CommentStreamWebSocketPush, CacheUpdater, AnalyticsTrackerNew comment posted
Stock Brokerage (#29)StockMarketMobileApp, EmailAlert, TradingBotPrice change
Pub-Sub (#11)Topic/PubSubManagerRegisteredSubscriber instancesMessage published
Chat (#37)ChatRoomUserConnectionsNew message in room
Metrics (#75)MetricCollectorDashboardUpdater, AlertEvaluator, LogWriterMetric recorded

Implementation

// โ”€โ”€โ”€ EXAMPLE 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// WHAT WE ARE IMPLEMENTING:
// An online store inventory monitor alerting waiting buyers immediately when
// an out-of-stock item is replenished.
//
// WHERE THE OBSERVER FITS IN:
// Subject is the Observable (e.g., Product). Observer is the Subscriber
// interface. Customer classes act as Concrete Observers.
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.concurrent.CopyOnWriteArrayList;

// --- Observer interface ---
interface StockObserver {
    void update(String symbol, double price);
}

// --- Subject ---
class StockMarket {
    private final java.util.List<StockObserver> observers = new CopyOnWriteArrayList<>();
    private final java.util.Map<String, Double> prices = new java.util.HashMap<>();

    public void addObserver(StockObserver obs) { observers.add(obs); }
    public void removeObserver(StockObserver obs) { observers.remove(obs); }

    public void setPrice(String symbol, double price) {
        prices.put(symbol, price);
        notifyObservers(symbol, price);
    }

    private void notifyObservers(String symbol, double price) {
        for (StockObserver obs : observers) {
            obs.update(symbol, price);
        }
    }
}

// --- Concrete observers ---
class MobileApp implements StockObserver {
    private final String name;
    public MobileApp(String name) { this.name = name; }
    public void update(String symbol, double price) {
        System.out.println("  [MobileApp " + name + "] " + symbol + " now $" + price);
    }
}

class EmailAlertService implements StockObserver {
    public void update(String symbol, double price) {
        if (price > 500) {
            System.out.println("  [EMAIL ALERT] " + symbol + " crossed $500 at $" + price);
        }
    }
}

class TradingBot implements StockObserver {
    public void update(String symbol, double price) {
        if (price < 50) {
            System.out.println("  [TradingBot] Buying " + symbol + " at $" + price);
        }
    }
}

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

        market.addObserver(new MobileApp("Alice"));
        market.addObserver(new MobileApp("Bob"));
        market.addObserver(new EmailAlertService());
        market.addObserver(new TradingBot());

        System.out.println("--- Price Update: AAPL ---");
        market.setPrice("AAPL", 150.0);

        System.out.println("\n--- Price Update: TSLA ---");
        market.setPrice("TSLA", 520.0);
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...