Observer: Typed Event Flow
Observer: Typed Event Flow
The observer lets publishers notify subscribers without knowing who they are. Constrain it with a payload map so every event carries its own type:
type Events = {
"order:placed": { id: string; total: number };
"user:seen": { userId: string };
};
class Bus {
private subs = new Map<keyof Events, ((p: any) => void)[]>();
on<K extends keyof Events>(k: K, fn: (p: Events[K]) => void) {
(this.subs.get(k) ?? this.subs.set(k, []).get(k)!).push(fn);
return () => this.off(k, fn);
}
}
Returning an unsubscribe function makes cleanup a one-liner in effects and request handlers.
Strategy: Algorithms Behind an Interface
Strategy: Algorithms Behind an Interface
Strategy extracts a swappable algorithm behind a contract. The function form is usually enough:
type Discount = (cents: number, tier: "bronze" | "gold") => number;
const rules: Record<string, Discount> = {
flat: (c) => c - 500,
tiered: (c, t) => (t === "gold" ? c * 0.8 : c * 0.95),
};
function charge(cents: number, tier: string, d: Discount) {
return Math.max(0, d(cents, tier as "bronze" | "gold"));
}
Interface-based strategies fit stateful collaborators (a payment gateway, an encoder); function strategies fit stateless rules. Selection can be data-driven — a registry keyed by config — so adding an option means adding an entry, not editing a conditional.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
Leaked listeners. Subscribing in a component or request path without unsubscribing grows memory and re-fires stale handlers; always dispose via the returned function (or AbortController for fetch-style listeners).
Error isolation. One throwing observer normally aborts the loop — wrap each handler so a bad subscriber cannot silence the rest.
Strategy sprawl. If strategies share hidden state or need the caller's internals, the abstraction is wrong: pass context as an argument or merge them.
Interview: sketch both patterns, then explain the difference the interviewer is probing — observer changes who reacts (many, dynamic), strategy changes how it computes (one, chosen per call).
Quiz
1. What should on(...) return to make cleanup reliable?
2. Observer versus strategy: what does each vary?
3. Why wrap observer handlers in try/catch during notify?
Flashcards
Question
Observer pattern
Click to reveal answer
Answer
Publisher notifies a list of subscribers it does not know; membership changes at runtime.
Question
Typed event map
Click to reveal answer
Answer
Record of event name → payload type; on<K> then infers payload per event.
Question
Strategy pattern
Click to reveal answer
Answer
One algorithm behind an interface or function type, swapped per call or per config.
Question
Listener leak
Click to reveal answer
Answer
Subscribing without disposing; handlers pile up and fire on stale state.
Revision Notes
Key Takeaways
- 1. Type the event map and return unsubscribe from subscribe
- 2. Prefer function strategies for stateless rules, interfaces for collaborators
- 3. Isolate handler errors so one subscriber cannot break delivery
Interview Tips
- • Contrast observer with the mediator/publisher-subscriber split the interviewer may push on
- • Show the registry form of strategy and explain why it beats an if/else chain
Cheat Sheet
on(k, fn) → typed payload via Events map, return off(); strategy = swappable function/interface selected per call; unsubscribe on dispose; try/catch per handler.