Skip to main content

Behavioral Patterns — Observer & Strategy

Level: Patterns | Track: Low Level Design

Why it matters in interviews

Observer and Strategy are the two behavioral patterns that appear most often in LLD system design rounds. Observer is the backbone of event-driven systems, notification frameworks, and reactive UIs. Strategy is how you avoid long if-else chains when business rules need to change at runtime. Naming these patterns quickly and sketching the interface correctly is a strong differentiator.

Core concepts

Observer (publish-subscribe) decouples event producers from consumers. A subject maintains a list of observers and calls a notify() (or update()) method on each when its state changes. Observers register and deregister at runtime.

  • Push model: subject passes event data directly in the notify call.
  • Pull model: subject passes itself (or minimal context); observer calls back to fetch what it needs.

Strategy encapsulates a family of interchangeable algorithms behind a common interface. The client holds a reference to a Strategy and can swap it at runtime without any conditional logic.

Key distinction: Observer is about who to tell; Strategy is about how to do it.

Code example

// ── OBSERVER: Parking Lot spot availability ───────────────────────────────────

interface ParkingObserver {
void onSpotAvailable(String spotId);
}

class ParkingLot {
private final List<ParkingObserver> observers = new ArrayList<>();
private final Set<String> availableSpots = new HashSet<>();

void addObserver(ParkingObserver o) { observers.add(o); }
void removeObserver(ParkingObserver o) { observers.remove(o); }

void freeSpot(String spotId) {
availableSpots.add(spotId);
observers.forEach(o -> o.onSpotAvailable(spotId)); // push notification
}
}

class MobileAppNotifier implements ParkingObserver {
public void onSpotAvailable(String spotId) {
System.out.println("Push notification: spot " + spotId + " is free");
}
}

class DisplayBoard implements ParkingObserver {
public void onSpotAvailable(String spotId) {
System.out.println("Board updated: " + spotId + " available");
}
}

// ── STRATEGY: Pricing ─────────────────────────────────────────────────────────

interface PricingStrategy {
double calculate(double basePrice);
}

class StandardPricing implements PricingStrategy {
public double calculate(double base) { return base; }
}

class PeakHourPricing implements PricingStrategy {
public double calculate(double base) { return base * 1.5; }
}

class DiscountPricing implements PricingStrategy {
private final double discountPct;
DiscountPricing(double pct) { this.discountPct = pct; }
public double calculate(double base) { return base * (1 - discountPct / 100); }
}

class OrderPricer {
private PricingStrategy strategy;

void setStrategy(PricingStrategy s) { this.strategy = s; } // swap at runtime

double getPrice(double base) { return strategy.calculate(base); }
}

When to use

SituationPattern
Multiple components must react when one object changes stateObserver
You want to decouple the event source from its consumersObserver
The set of listeners can change at runtime (subscribe/unsubscribe)Observer
Business rules vary per customer, time of day, or feature flagStrategy
You have a growing if/else or switch on algorithm typeStrategy
You want to unit-test each algorithm in isolationStrategy

Decision shortcut: "If you need to notify multiple components of state changes, that's Observer. If you need to swap algorithms at runtime, that's Strategy."

Common interview mistakes

  • Forgetting unsubscribe / memory leaks: Always mention that observers should be removable. Interviewers ask "what happens when the user logs out?" — you need removeObserver.
  • Conflating Observer with Event Bus: They're related but not identical. An EventBus is a global broker; classic Observer has a direct subject-observer relationship. Know when to use which.
  • Hardcoding the algorithm in the context class: The whole point of Strategy is that the context should not know which algorithm runs. If you have if (strategy == "peak") { ... } in the context, you've missed the pattern.
  • Making Strategy stateless when it needs state: DiscountPricing above holds a discountPct field. Strategy objects can be stateful — don't strip out constructor parameters to keep things "simple."

Covered by Problems

ProblemLink
Ride Sharing System
Parking Lot System
Library Management System

Resources