Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 33

    Observer Pattern

    Observer (Publish/Subscribe at object level) decouples event sources from handlers.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Observer (Publish/Subscribe at object level) decouples event sources from handlers. A Subject maintains Observers and notifies them on state change. Modern variants: EventEmitter, domain events, RxJS, React subscriptions, Kafka topics. The pattern is how systems react without polling.

    The story

    A bank polled five downstream APIs every three seconds for loan status — under peak load the polling storm took down notification service. Domain events on state change cut API traffic 78% and gave customers instant updates.

    The business problem

    Tight coupling and polling create traffic storms and Open/Closed violations:

    • Polling tax: downstream APIs hammered checking for changes that haven't happened.
    • Edit-the-source: new reaction (audit, email) means editing publisher.
    • Cascade coupling: publisher can't ship without coordinating every consumer.
    • Missed reactions: teams forget to wire new side effects.

    The problem teams faced

    Observer fits when:

    • Multiple subsystems react when one object's state changes.
    • Adding reactions should not edit the source (Open/Closed).
    • Polling exists because no push mechanism does.
    • Reactions vary: email, analytics, audit, projection update.

    Understanding the topic

    Intent: One-to-many dependency — when subject changes, observers update automatically.

    • Subject — attach/detach/notify observers.
    • Observer — update(event) handler interface.
    • Domain Event — modern variant: immutable fact emitted on change.
    • Event Bus — decouples in-process or cross-service pub/sub.

    Internal architecture

    Domain event + subscribers:

    ts
    class Order {
    private listeners = new Set<OrderListener>()
    subscribe(l: OrderListener) { this.listeners.add(l) }
    private emit(e: OrderEvent) { this.listeners.forEach(l => l.onOrderEvent(e)) }
    ship() {
    this.status = "SHIPPED"
    this.emit({ type: "SHIPPED", orderId: this.id, at: new Date() })
    }
    }

    Visual explanation

    Three diagrams cover notify flow, vs polling, and sync vs async:

    Observer notify flow
    Subject state change
    Order.ship()
    Build event
    OrderShipped
    Notify observers
    forEach update
    Handlers react
    Email · Audit · BI
    Publisher emits fact; subscribers react without publisher knowing details.
    Push vs poll
    State changes rarely?
    Push wins
    Poll every 3s
    78% wasted calls
    Event on change
    Notify once
    Instant + efficient
    Observer
    Polling is Observer failure mode — push on change instead.
    In-process vs message bus
    Same process?
    EventEmitter
    Cross service?
    Kafka · SNS
    Fast handlers
    In-process sync
    Slow / external
    Async bus
    Don't run slow I/O synchronously in notify loop — async or outbox.

    Informative example

    Before / after — polling to domain events:

    ts
    // ❌ Poll five APIs every 3 seconds
    setInterval(async () => {
    for (const api of downstreamApis) {
    await api.checkLoanStatus(loanId) // 78% calls find no change
    }
    }, 3000)
    // ✅ Emit on change — subscribers react once
    class LoanApplication {
    approve() {
    this.status = "APPROVED"
    this.events.publish({ type: "LoanApproved", loanId: this.id })
    }
    }
    events.subscribe("LoanApproved", {
    notifyCustomer: (e) => emailService.send(e.loanId),
    updateBI: (e) => biPipeline.ingest(e),
    audit: (e) => auditLog.write(e),
    })

    Execution workflow

    1Observer lifecycle
    1 / 4

    Subscribe

    Observers attach at init or runtime.

    Unsubscribe on dispose — prevent leaks.

    Real-world use

    Node EventEmitter, Spring ApplicationEventPublisher, React useState re-renders, Redux dispatch, Kafka consumers, DDD domain events, GUI event listeners.

    Production case study

    Logistics "shipment delivered" reactions:

    • Scenario: Five teams needed to react to delivery events.
    • Decision: Domain event in-process for audit; Kafka for billing and BI.
    • Outcome: Sixth reaction needed zero publisher changes.

    Trade-offs

    • Pro: Open/Closed for new reactions; eliminates polling.
    • Pro: publisher decoupled from subscriber details.
    • Con: notification order non-deterministic unless specified.
    • Con: debugging event cascades harder than direct calls.
    • Con: memory leaks if observers don't unsubscribe.

    Decision framework

    • Use when multiple independent reactions to same state change.
    • Use when polling cost exceeds push infrastructure cost.
    • Avoid when single synchronous caller — direct call simpler.
    • Avoid Observer-for-everything without Command/State discipline.

    Best practices

    • Immutable domain events — facts, not commands.
    • Unsubscribe on component dispose / request end.
    • Async or outbox for slow subscribers — don't block publisher.
    • Idempotent handlers for at-least-once delivery.

    Anti-patterns to avoid

    • Observer for everything — no trace of who started flow.
    • Handlers mutating each other — circular cascades.
    • Synchronous email send inside notify loop.

    Common mistakes

    • Memory leak from forgotten unsubscribe.
    • Event handler throws — kills other subscribers unless isolated.
    • Ordering assumptions across async handlers.

    Debugging tips

    • Correlation ID on every event — trace cascade across handlers.
    • Log subscriber count and handler duration per event type.

    Optimization strategies

    • Batch events for high-frequency state changes (debounce UI).
    • Separate hot path (in-process) from cold path (message bus).

    Common misconceptions

    • Observer ≠ Event Sourcing. Observer notifies; ES stores events as source of truth.
    • Mediator coordinates; Observer broadcasts — different coupling.
    • Kafka is Observer at network scale.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    4 questions
    1IntermediateQuestionObserver vs Pub/Sub vs Event Bus?+

    Answer

    Same family at different scales. Observer: object-level attach/notify. Event bus: decoupled named channels. Pub/sub: often cross-process.

    Follow-up

    When Kafka over EventEmitter?
    2AdvancedQuestionSync vs async notification?+

    Answer

    Sync OK for fast in-process handlers. Async/outbox for I/O, cross-service, or when handler failure shouldn't block publisher.

    Follow-up

    Outbox pattern?
    3IntermediateQuestionMemory leak in Observer?+

    Answer

    Forgotten unsubscribe — observer held by subject after component destroyed. Fix: detach on dispose, weak refs, or scoped event bus per request.

    Follow-up

    React useEffect cleanup?
    4AdvancedQuestionObserver vs Mediator?+

    Answer

    Observer: subject broadcasts to many; doesn't coordinate replies. Mediator: central orchestrator; colleagues talk through it.

    Follow-up

    Chat room example?

    Summary

    You can replace polling with domain events, wire subscribers without editing publishers, and avoid cascade anti-patterns. Tell the loan-status polling storm story — if you can do that, you own Observer.

    Ready to mark this lesson complete?Track your journey across the entire course.