SwadhyAI Notes

All Low-Level Design notes

Behavioral · Gang of Four

Observer

Observer is a behavioral pattern for one-to-many notification. A subject announces an event; listeners subscribe. The subject never imports the listener classes by name.

Example: a goal is scored. The scoreboard must update, the stats store must write a row, and a clipper may start. If Match calls all three by name, the next listener (SMS, replay camera) still edits Match. If Match only announces “goal scored,” a new listener is a new GoalObserver class.

The subject never names the listeners

Observer is a that keeps a list of observers and notifies them when something happens. In-process pub/sub is Observer with topic strings. The HLD pub/sub case study is brokers and delivery guarantees — keep broker delivery guarantees on the system-design page, not in this LLD note.

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart LR
    M[Match] -->|onGoal| S[Scoreboard]
    M -->|onGoal| T[StatsStore]
    M -->|onGoal| C[Clipper]
    M -.->|does not import| X[next listener]
Step 1

Name the event

Goal, OrderPaid, SpotOccupied — a fact, not a procedure.

Step 2

Hold Observer, not concretes

subscribe / unsubscribe / notify. Match never imports Scoreboard.

Step 3

Listeners are cheap to add

A new Clipper implements the interface. Match does not change.

Observer vs Mediator

Broadcast news versus traffic control

Observer is one-to-many news: the subject does not coordinate the listeners, and the listeners do not need to talk to each other. Mediator is a hub among peers that must. If widgets enable each other, use Mediator. If many objects only need to hear “order paid,” use Observer.

Observer

Match ──notifies──> A, B, C. The is Goal. Scoreboard does not ask Stats to update.

Mediator

Name, Date, OK all talk through BookingDialog. The hub enables OK when two fields agree. That is not a Goal event.

Java: Match does not name Scoreboard

Prefer CopyOnWriteArrayList (or a synchronized snapshot) if subscribe and publish can race. On a single-threaded Match, a plain list plus List.copyOf is enough — state that assumption clearly. Isolate listener failures so one bad StatsStore does not silence Scoreboard. Pair subscribe with unsubscribe on close. Keep the event small. A notification fan-out is the same shape.

java
interface GoalObserver {
  void onGoal(Goal goal);
}

class Match {
  // Single-threaded Match: ArrayList + snapshot is fine.
  // Concurrent subscribe/publish: use CopyOnWriteArrayList instead.
  private final List<GoalObserver> observers = new CopyOnWriteArrayList<>();

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

  void score(Player scorer, int minute) {
    Goal goal = new Goal(scorer, minute);
    for (GoalObserver o : observers) {
      try {
        o.onGoal(goal);
      } catch (RuntimeException e) {
        // Failure isolation: one bad listener must not silence the rest.
        log.warn("observer failed", e);
      }
    }
  }
}

class Scoreboard implements GoalObserver {
  public void onGoal(Goal goal) { /* redraw */ }
}

class StatsStore implements GoalObserver {
  public void onGoal(Goal goal) { /* insert row */ }
}

Notify under a lock, listener calls back

score() holds the match lock, Scoreboard calls match.scoreboardText(), which takes the same lock — or mutates the observer list while you iterate. Copy the list, release, then notify. Watch . This is also a design smell when the event is the whole Match.

Forgotten unsubscribe

A UI panel that subscribes on open and never unsubscribes keeps the Match alive, or keeps a dead panel receiving goals. Pair subscribe with a lifecycle close.

The subject imports every listener

If Match has fields for Scoreboard, Stats, and Sms, the next listener still edits Match. Depend on the interface.

Mixing HLD pub/sub into an LLD interview

Partitions, at-least-once, and consumer groups are the system-design note. Here you are naming an in-process list and an event type.

Snapshot, notify, isolate failures

The event is a fact: Goal(scorer, minute). Listeners must not need the whole Match. If Scoreboard calls match.getEverything(), you leaked the subject. Keep payloads small so notify stays cheap and lock-free.

Subscribe

Add to the list. Pair with a close() that unsubscribes.

Snapshot / COW

CopyOnWriteArrayList, or List.copyOf under a lock, if threads share Match.

Notify

Call onGoal(goal). Isolate failures. Do not hold the match lock.

Java: order-paid is the same shape

Email, ledger, and analytics subscribe. Order does not import Emailer. A new SMS listener is a new class.

java
interface OrderPaidObserver {
  void onPaid(OrderPaid event);
}

class Order {
  private final List<OrderPaidObserver> observers = new ArrayList<>();
  void subscribe(OrderPaidObserver o) { observers.add(o); }

  void markPaid(Instant at) {
    OrderPaid event = new OrderPaid(id, at);
    for (OrderPaidObserver o : List.copyOf(observers)) o.onPaid(event);
  }
}

In-process vs broker

This page is a list in memory. Kafka partitions and consumer groups live on the system-design pub/sub note. If the interviewer wants both, draw the in-process Observer first, then say the broker is the same idea across machines.

Synchronous notify

Default is sync: a slow StatsStore stalls the goal. If that matters, queue the event. Do not pretend Match is async because you named Observer.

How to explain this in an interview

“Match depends on GoalObserver, not Scoreboard. A new listener is a new class.” Then mention CopyOnWrite (or a single-threaded assumption), unsubscribe on close, and failure isolation so they know you have seen the sharp edges. Widgets that enable each other are still Mediator, not a Goal event. Keep the event small: scorer and minute, not the whole Match.

Brokers, partitions, and at-least-once delivery stay on the system-design note. Here the list is in process. Notify is synchronous unless you queue; a slow StatsStore stalls the goal.

Nearby in LLD