Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,050visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsBehavioral Design Patterns
✓ FreeBeginner· 8 min read

Observer Pattern

The subject-notifies-observers pattern behind most event systems — push vs pull models, and the classic memory leak from un-removed observers.

Published September 23, 2026


Observer Pattern

The idea: one-to-many state-change notification

A subject maintains a list of observers and notifies all of them when its state changes — neither side needs to know the other's concrete type, only the shared Observer contract.

interface StockObserver { void onPriceChange(String symbol, double newPrice); }

class StockPriceTracker { // the subject
    private final List<StockObserver> observers = new ArrayList<>();
    private double price;

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

    void updatePrice(String symbol, double newPrice) {
        this.price = newPrice;
        for (StockObserver o : observers) o.onPriceChange(symbol, newPrice); // notify everyone
    }
}

class DisplayPanel implements StockObserver {
    public void onPriceChange(String symbol, double price) { System.out.println(symbol + ": " + price); }
}
class PriceAlertService implements StockObserver {
    public void onPriceChange(String symbol, double price) { if (price > 100) triggerAlert(symbol); }
    private void triggerAlert(String symbol) { /* ... */ }
}
StockPriceTracker tracker = new StockPriceTracker();
tracker.subscribe(new DisplayPanel());
tracker.subscribe(new PriceAlertService());
tracker.updatePrice("AAPL", 150.0); // both subscribers react independently

Neither DisplayPanel nor PriceAlertService know about each other, and StockPriceTracker doesn't know what its observers actually do with the notification — it just broadcasts. This is the foundation almost every event/pub-sub system builds on, from GUI listeners to message queues.

Advertisement

Push vs pull models

The example above is push: the subject sends the full new state (symbol, newPrice) directly in the notification. The alternative is pull: the subject only sends a minimal "something changed" signal, and the observer calls back into the subject to fetch whatever specific data it needs.

interface PullObserver { void onUpdate(StockPriceTracker subject); } // subject reference, not data

class DisplayPanel implements PullObserver {
    public void onUpdate(StockPriceTracker subject) {
        double price = subject.getCurrentPrice(); // observer decides what it needs, and pulls it
    }
}

Push is simpler and cheaper when every observer wants roughly the same data. Pull is more flexible when different observers care about different subsets of state, avoiding the subject having to know every possible thing an observer might want.

The classic Observer leak: un-removed observers

class ExpensiveService {
    ExpensiveService(StockPriceTracker tracker) {
        tracker.subscribe(this::onPriceChange); // subscribed, but never unsubscribed
    }
    private void onPriceChange(String s, double p) { /* ... */ }
}

If ExpensiveService instances are meant to be short-lived but the StockPriceTracker is long-lived, every ExpensiveService that never calls unsubscribe() stays referenced forever by the tracker's observer list — the garbage collector can't reclaim it, since the tracker still holds a live reference. This is a textbook listener/callback leak (also called out in Memory Leaks in Java): the fix is either explicit unsubscription in a lifecycle hook (close(), @PreDestroy), or using weak references for the observer list so the GC can collect observers that nothing else holds onto.

A robust subject: thread safety, failure isolation and unsubscription

The textbook subject keeps a list and loops over it. Production code has to answer three awkward questions: what if observers are added or removed during notification, what if one observer throws, and how does an observer reliably unsubscribe?

public final class EventSource<E> {
    private final List<Consumer<? super E>> listeners = new CopyOnWriteArrayList<>();

    /** Returns a handle: closing it unsubscribes. No need to keep the original lambda around. */
    public AutoCloseable subscribe(Consumer<? super E> listener) {
        listeners.add(listener);
        return () -> listeners.remove(listener);
    }

    public void publish(E event) {
        for (Consumer<? super E> l : listeners) {           // iterates a snapshot: safe under concurrent changes
            try {
                l.accept(event);
            } catch (RuntimeException e) {
                log.warn("Listener {} failed on {}", l, event, e);   // one bad listener doesn't stop the rest
            }
        }
    }
}
  • CopyOnWriteArrayList makes iteration work on a snapshot, so observers can subscribe or unsubscribe during a notification (even from inside a callback) without ConcurrentModificationException. It fits because listener lists are read constantly and changed rarely.
  • Isolating failures: without the try/catch, an exception in the second observer means the third and fourth never hear about the event, which is a hard-to-trace inconsistency.
  • A subscription handle (AutoCloseable) solves a classic bug: remove(this::onEvent) creates a new method-reference object, which never equals the one that was added, so it silently removes nothing. Returning a handle from subscribe avoids the problem, and fits try-with-resources for scoped subscriptions.

Synchronous vs asynchronous notification

The version above notifies synchronously, on the publisher's thread:

SynchronousAsynchronous (executor or queue)
Publisher speedWaits for every observerReturns immediately
OrderingObservers see events in publish orderNeeds care, since parallel delivery can reorder
TransactionsObservers run inside the publisher's transactionObservers run later, outside it
FailuresVisible to the publisher, if you let them throughMust be handled and logged by the delivery mechanism

An async variant hands each notification to an executor. To keep per-observer ordering, use one single-threaded executor or queue per observer, rather than a shared pool.

Observer in Spring: application events

Spring's event system is the Observer pattern with the wiring done for you:

public record OrderPlaced(String orderId, String customerId) {}

@Service
class OrderService {
    private final ApplicationEventPublisher events;
    OrderService(ApplicationEventPublisher events) { this.events = events; }

    @Transactional
    public void place(Order order) {
        repository.save(order);
        events.publishEvent(new OrderPlaced(order.id(), order.customerId()));
    }
}

@Component
class OrderEmailListener {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)   // only if the order really committed
    @Async
    public void on(OrderPlaced e) { emailService.sendConfirmation(e.orderId()); }
}

@TransactionalEventListener(AFTER_COMMIT) addresses a subtle problem: a plain @EventListener runs immediately, inside the transaction, so an email could be sent for an order that then rolls back. Waiting for the commit ensures observers only react to changes that actually happened. For events that must also survive a crash between the commit and the listener running, use the transactional outbox pattern.

When Observer becomes a problem

  • Hidden control flow: "what happens when an order is placed?" is no longer answerable by reading place(). Keep event names descriptive, and document the important listeners.
  • Cascades: observers that publish further events can create long, or even circular, chains. Keep event handlers small, and avoid re-publishing the same event type.
  • Memory leaks: a long-lived subject holding references to short-lived observers keeps them alive. Always unsubscribe when the observer's lifetime ends (a UI component closing, a bean being destroyed).

Follow-up questions this topic invites — and their answers

Q: Is Java's own PropertyChangeListener an Observer implementation? A: Yes — java.beans.PropertyChangeSupport is essentially a built-in Observer subject, and Swing/AWT's event listener model (ActionListener, etc.) follows the same shape throughout the JDK.

Q: How does Observer relate to a message queue / event bus in a backend system? A: Same core idea at a different scale and with different delivery guarantees — a message queue decouples publisher and subscriber across processes/services (with persistence, retry, and ordering concerns Observer doesn't address in-process), but the fundamental "notify interested parties without either side knowing the other's concrete type" relationship is identical.

Q: What happens if an observer's callback throws an exception during notification? A: In the naive loop shown above, an exception in one observer's onPriceChange would propagate up and prevent any later observers in the list from being notified at all — production Observer implementations typically wrap each observer call in its own try/catch so one misbehaving observer can't break notification for the rest.

Q: Would you use push or pull for a UI that shows a live dashboard of many different metrics? A: Pull tends to fit better there — different dashboard widgets likely care about different subsets of the subject's state, and pushing every possible field to every observer regardless of relevance wastes both bandwidth and each observer's effort filtering out data it doesn't need.

Q: Why does remove(this::handler) often fail to unsubscribe? A: Each evaluation of a method reference or lambda can create a new object, and it's not equal to the one registered earlier, so the list removes nothing. Keep the original reference, or better, have subscribe return a handle that removes exactly what was added.

Q: Why use @TransactionalEventListener instead of @EventListener for side effects like emails? A: A plain listener runs during the transaction, so it can act on changes that are later rolled back. @TransactionalEventListener(phase = AFTER_COMMIT) runs only once the transaction has committed, so observers react to real, durable changes.

Q: Is CopyOnWriteArrayList always the right choice for observer lists? A: It's ideal when notifications are frequent and subscription changes are rare, because iteration needs no locking. If observers change very often (thousands per second), each change copies the array, so a concurrent set or a lock-protected list may perform better.

Advertisement

Previous

Strategy Pattern

Next

Command Pattern

AI Tutor

Lesson: Observer Pattern

Quick actions

AI responses can be inaccurate. Verify critical information.