Observer means: one object (the subject) announces “something changed,” and every object that depends on it (the observers) gets notified automatically — without the subject knowing who they are.
Observers subscribe once. From then on, the subject just broadcasts to whoever is currently listening. It never keeps a hardcoded list of specific classes to call.
Three pieces work together:
A subject — keeps a list of observers; can register, remove, and notify them.
An observer interface — every subscriber implements it, usually one update method.
Concrete observers — react however they want; the subject does not need to know how.
The problem it solves
Without Observer, a subject that needs to inform several unrelated parts of a program tends to call each one directly.
def publish_breaking_news(headline: str) -> None: cnn_channel.display(headline) bbc_channel.display(headline) email_alert_service.send(headline) # add a fourth consumer? edit this function again.
public void publishBreakingNews(String headline) { cnnChannel.display(headline); bbcChannel.display(headline); emailAlertService.send(headline); // add a fourth consumer? edit this method again.}
func PublishBreakingNews(headline string) { cnnChannel.Display(headline) bbcChannel.Display(headline) emailAlertService.Send(headline) // add a fourth consumer? edit this function again.}
function publishBreakingNews(headline: string): void { cnnChannel.display(headline); bbcChannel.display(headline); emailAlertService.send(headline); // add a fourth consumer? edit this function again.}
This works while the list of interested parties is small and stable. It breaks down the moment that list changes often, because every addition or removal means editing code that has nothing to do with the actual news — it is just wiring.
NewsAgency never mentions NewsChannel by name. A third, fourth, or fifth observer can register at any time, and publish does not change at all.
Push versus pull
There are two ways to hand data to observers. In push, shown above, the subject sends the data directly in the update call — simple, but every observer gets the same payload. In pull, the subject just signals that something changed, and each observer calls back to fetch exactly what it needs.
class Observer(ABC): @abstractmethod def update(self, subject: "Subject") -> None: ...# inside a concrete observerdef update(self, subject: "StockMarket") -> None: price = subject.get_price("AAPL") # fetch only what this observer needs
public interface Observer { void update(Subject subject);}// inside a concrete observerpublic void update(Subject subject) { double price = ((StockMarket) subject).getPrice("AAPL"); // fetch only what this observer needs}
type Observer interface { Update(subject *StockMarket)}// inside a concrete observerfunc (t *Trader) Update(subject *StockMarket) { price := subject.GetPrice("AAPL") // fetch only what this observer needs _ = price}
interface Observer { update(subject: Subject): void;}// inside a concrete observerupdate(subject: StockMarket) { const price = subject.getPrice('AAPL'); // fetch only what this observer needs}
Pull suits subjects with a lot of state where different observers care about different slices of it. Push suits simple notifications where the data is small and every observer needs the same thing.
Worked example: stock price notifications
Several independent traders react to price changes in a shared market.
StockMarket has no idea how many traders exist or what they do with a price update — it just broadcasts. That is the whole point.
A note for Java readers: java.util.Observable exists but has been deprecated since Java 9, because it is a class (not an interface, so it uses up your one shot at inheritance) and is not thread-safe. Write your own small Subject/Observer interfaces as above, or reach for a reactive library for anything more elaborate than a handful of listeners.
When it helps
Observer fits wherever one change needs to ripple out to a variable, possibly-changing set of listeners: UI components reacting to model updates, logging or metrics hooks kept out of core business logic, cache invalidation, any publish/subscribe workflow.
When it hurts
Notification is indirect, so tracing “what happens when this value changes” means following every registered observer. If an observer’s update triggers a change back on the subject, you can get notification loops that are hard to debug.
There are two common traps. An observer that is never removed keeps a live reference to the subject (and vice versa), which leaks memory in long-running apps. And if one observer throws during notification, a naive loop can stop the rest from being notified — always isolate failures per observer.
For a handful of tightly related objects, a plain method call is simpler than wiring up a subject/observer relationship.
Practical tips
Keep the observer interface small — usually one method.
Choose push or pull deliberately, based on how much state observers actually need.
Wrap each observer’s update call so one broken listener cannot block the others.
Always provide a way to unregister, and call it.
Never notify observers from inside a constructor — the object is not fully built yet.
Check your understanding
Observer in one line?
One object notifies many dependent objects automatically when it changes.
Push vs pull?
Push sends the data in the notification. Pull just signals a change and lets observers fetch what they need.
Why is Java’s Observable class discouraged?
It is a class (not an interface), is not thread-safe, and has been deprecated since Java 9.
What are the two most common Observer bugs?
Forgetting to unregister an observer (a leak), and letting one observer’s exception stop the rest.
When should you avoid it?
When the relationship is a simple, fixed one-to-one call — a direct method call is easier to follow.
What to learn next
Next: State Pattern — let an object change its behavior when its internal state changes.