Skip to content
intermediatePhase 49 · Low-Level Design

Observer Pattern

Implement publish-subscribe for event-driven communication.

30m
0 problems
Topic Progress0%

Subject and Observer

The Observer pattern defines a one-to-many dependency so that when one object changes state, all dependents are notified.

Core Structure

┌──────────────────────────┐
│      Subject (Publisher) │
├──────────────────────────┤
│ - observers: List        │
│ - state: State           │
├──────────────────────────┤
│ + attach(observer)       │
│ + detach(observer)       │
│ + notify()               │
└──────────┬───────────────┘
           │ notifies
     ┌─────┴──────┐
     │            │
┌────┴─────┐ ┌───┴──────┐
│ObserverA │ │ObserverB │
└──────────┘ └──────────┘

Observer Interface

// Observer interface
public interface Observer {
    void update(Event event);
}

// Subject interface
public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

Subject Implementation

public class OrderService implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private OrderStatus status;
    
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(new OrderEvent(this.status));
        }
    }
    
    public void completeOrder(Order order) {
        // Process order...
        this.status = OrderStatus.COMPLETED;
        notifyObservers();
    }
}

Observer Implementations

public class EmailObserver implements Observer {
    @Override
    public void update(Event event) {
        if (event.getType() == OrderEvent.Type.COMPLETED) {
            emailService.send(event.getUser(), "Order complete!");
        }
    }
}

public class InventoryObserver implements Observer {
    @Override
    public void update(Event event) {
        if (event.getType() == OrderEvent.Type.COMPLETED) {
            inventoryService.reserve(event.getItems());
        }
    }
}

public class AnalyticsObserver implements Observer {
    @Override
    public void update(Event event) {
        analytics.track("order_completed", event.getData());
    }
}

Usage

OrderService orderService = new OrderService();

// Register observers
orderService.attach(new EmailObserver());
orderService.attach(new InventoryObserver());
orderService.attach(new AnalyticsObserver());

// When order completes, all observers are notified
orderService.completeOrder(order);

Implementation

Different implementation approaches for the Observer pattern.

Push Model vs Pull Model

// Push Model: Subject sends data to observers
public interface Observer {
    void update(Order order, OrderStatus status, Map<String, Object> data);
}

// Pull Model: Observers pull what they need
public interface Observer {
    void update(Subject subject);
}

public class OrderObserver implements Observer {
    @Override
    public void update(Subject subject) {
        OrderService orderService = (OrderService) subject;
        OrderStatus status = orderService.getStatus(); // Pull what needed
    }
}

Thread-Safe Observer

public class ThreadSafeSubject implements Subject {
    private final List<Observer> observers = 
        Collections.synchronizedList(new ArrayList<>());
    
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        synchronized (observers) {
            for (Observer observer : observers) {
                observer.update(getState());
            }
        }
    }
}

Event-Based Observer

// Generic event system
public class EventBus {
    private Map<Class<?>, List<Consumer<?>>> listeners = new HashMap<>();
    
    public <T> void subscribe(Class<T> eventType, Consumer<T> listener) {
        listeners.computeIfAbsent(eventType, k -> new ArrayList<>())
                 .add(listener);
    }
    
    public <T> void publish(T event) {
        List<Consumer<?>> eventListeners = listeners.get(event.getClass());
        if (eventListeners != null) {
            for (Consumer<?> listener : eventListeners) {
                ((Consumer<T>) listener).accept(event);
            }
        }
    }
}

// Usage
EventBus bus = new EventBus();

bus.subscribe(OrderCompletedEvent.class, event -> {
    emailService.send(event.getUser(), "Order complete!");
});

bus.subscribe(OrderCompletedEvent.class, event -> {
    inventoryService.reserve(event.getItems());
});

bus.publish(new OrderCompletedEvent(order));

Weak Reference Observer

// Prevent memory leaks with weak references
public class WeakSubject {
    private List<WeakReference<Observer>> observers = new ArrayList<>();
    
    public void attach(Observer observer) {
        observers.add(new WeakReference<>(observer));
    }
    
    public void notifyObservers() {
        observers.removeIf(ref -> ref.get() == null); // Clean dead refs
        for (WeakReference<Observer> ref : observers) {
            Observer observer = ref.get();
            if (observer != null) {
                observer.update(getState());
            }
        }
    }
}

Event Systems

Real-world event systems built on the Observer pattern.

E-Commerce Event System

// Event hierarchy
public interface DomainEvent {
    String getAggregateId();
    Instant getTimestamp();
}

public class OrderEvent implements DomainEvent {
    public enum Type { CREATED, PAID, SHIPPED, DELIVERED, CANCELLED }
    private final Type type;
    private final String orderId;
    private final Map<String, Object> data;
    // ...
}

// Event handlers
public class OrderEventHandler {
    @EventHandler
    public void onOrderCreated(OrderEvent event) {
        inventoryService.reserve(event.getItems());
    }
    
    @EventHandler
    public void onOrderPaid(OrderEvent event) {
        paymentService.confirm(event.getPaymentId());
    }
    
    @EventHandler
    public void onOrderShipped(OrderEvent event) {
        notificationService.sendTracking(event.getUser(), event.getTrackingId());
    }
}

Message Queue Observer

// Distributed observer using message queue
public class MessageQueueObserver implements Observer {
    private final MessageProducer producer;
    
    @Override
    public void update(Event event) {
        // Publish to message queue for distributed observers
        producer.send("order-events", event.toJson());
    }
}

// Observers in different services consume events
public class InventoryEventHandler {
    @KafkaListener(topics = "order-events")
    public void handle(OrderEvent event) {
        if (event.getType() == Type.COMPLETED) {
            inventoryService.reserve(event.getItems());
        }
    }
}

Observer Pattern in Frameworks

Framework Implementation
Java PropertyChangeListener, EventListener
Spring ApplicationEvent, @EventListener
React State management (Redux-like)
Node.js EventEmitter
Android LiveData, Observer interface

Event System Best Practices

  1. Keep events immutable: Don't modify events after creation
  2. Include context: Aggregate ID, timestamp, user info
  3. Handle failures: What if observer throws exception?
  4. Order matters: If order of notification matters, document it
  5. Memory management: Detach observers when done
  6. Async option: Consider async notification for performance

Practice Problems

0/3solved
Design Observer Pattern System

Design a scalable Observer Pattern system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Observer Pattern Scaling

How would you scale Observer Pattern to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Observer Pattern Failure Modes

Analyze potential failure modes for Observer Pattern and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What is the Observer pattern?

Question 1 options

2. What is the difference between push and pull models?

Question 2 options

3. What is a risk of the Observer pattern?

Question 3 options

4. How does the Observer pattern support loose coupling?

Question 4 options

5. Which is an example of Observer pattern?

Question 5 options

Flashcards

Question

What is the Observer pattern?

Answer

Defines a one-to-many dependency. When subject changes state, all observers are notified. Enables loose coupling between publisher and subscribers.

Question

Push vs Pull model in Observer?

Answer

Push: subject sends all data to observers. Pull: subject notifies, observers pull what they need. Pull gives observers more control.

Question

What is a memory leak risk in Observer?

Answer

If observers aren't detached when no longer needed, they remain referenced by the subject, preventing garbage collection.

Question

How to implement Observer in Java?

Answer

PropertyChangeListener, EventListener interface, or custom Observer interface. Spring uses @EventListener annotation.

Question

Name real-world Observer examples.

Answer

GUI event listeners, message queues, React state, Spring events, order status notifications, social media feeds.

Revision Notes

Key Takeaways

  • 1.Observer defines one-to-many dependency for automatic notifications
  • 2.Subject knows observers only through the Observer interface (loose coupling)
  • 3.Push model sends data; pull model lets observers request data
  • 4.Always detach observers to prevent memory leaks
  • 5.Observer is the foundation of event-driven systems

Interview Tips

  • Show Observer pattern for event notification systems
  • Discuss push vs pull trade-offs for your use case
  • Mention memory management when discussing Observer
  • Show how Observer enables loose coupling between components

Cheat Sheet

Observer Pattern - Cheat Sheet

Structure:

Subject (Publisher)
├── attach(observer)
├── detach(observer)
└── notify()
      │
      ▼
Observer (Subscriber)
└── update(event)

Models:

  • Push: subject sends data
  • Pull: observers request data

Key Points:

  • Loose coupling (subject only knows interface)
  • One-to-many notification
  • Dynamic registration/deregistration
  • Risk: memory leaks if not detached

Use Cases:

  • Event handling systems
  • Order status notifications
  • Social media feeds
  • Message queues
  • GUI frameworks

Implementation:

  1. Define Observer interface
  2. Subject maintains observer list
  3. Notify on state change
  4. Detach when done