SE

Behavioral patterns

Must-know concept1.3 hIntermediate

Observer, Strategy, Chain of Responsibility.

Behavioral patterns describe how objects talk. Three of them — Observer, Strategy, and Chain of Responsibility — quietly run almost every production system you'll touch. Learn to spot them and you can rename half the code you've already written.

The big idea

These three are the workhorses:

Observerbroadcast to listeners
Strategyswap an algorithm
Chain of responsibilitypipeline of handlers

Observer — pub/sub at the object level

One subject, many observers. The subject doesn't know who is listening; observers subscribe and react.

type Listener<T> = (event: T) => void;
 
class EventBus<T> {
  private listeners = new Set<Listener<T>>();
  on(fn: Listener<T>)  { this.listeners.add(fn);    return () => this.listeners.delete(fn); }
  emit(event: T)       { for (const fn of this.listeners) fn(event); }
}
 
const bus = new EventBus<{ type: 'order.placed'; orderId: number }>();
bus.on(e => sendEmail(e.orderId));
bus.on(e => updateAnalytics(e));
bus.emit({ type: 'order.placed', orderId: 9001 });

Every reactive UI framework (React, Vue, Svelte), every event-driven backend, every WebSocket client is Observer under the hood.

Strategy — swap an algorithm at runtime

When the what is fixed but the how varies, parameterise the how.

interface SortStrategy<T> {
  sort(items: T[]): T[];
}
 
class ByPrice implements SortStrategy<Item>  { sort(xs: Item[]) { return [...xs].sort((a,b) => a.price - b.price); } }
class ByPopularity implements SortStrategy<Item> { sort(xs: Item[]) { return [...xs].sort((a,b) => b.views - a.views); } }
class ByRelevance implements SortStrategy<Item>  { sort(xs: Item[]) { return mlRanked(xs); } }
 
function listResults(items: Item[], strategy: SortStrategy<Item>) {
  return strategy.sort(items);
}

The strategy is often just a function, no class:

type SortFn = (a: Item, b: Item) => number;
const byPrice: SortFn = (a, b) => a.price - b.price;
items.sort(byPrice);

Same pattern, less ceremony.

Chain of Responsibility — pipelines of handlers

A request passes through an ordered list of handlers. Each one decides to handle, modify, or pass along. Stop me if you've seen this:

type Handler = (req: Request, next: () => Promise<Response>) => Promise<Response>;
 
async function run(req: Request, handlers: Handler[]): Promise<Response> {
  let i = -1;
  const next = async () => {
    i++;
    if (i >= handlers.length) throw new Error('No handler completed');
    return handlers[i](req, next);
  };
  return next();
}
 
const stack: Handler[] = [
  async (req, next) => { log(req); return next(); },
  async (req, next) => { authenticate(req); return next(); },
  async (req, next) => { validate(req);     return next(); },
  async (req)       => actualHandler(req),
];
 
await run(incomingRequest, stack);

Every web framework's middleware pipeline (Express, Koa, FastAPI dependencies, NestJS interceptors) is this pattern.

Other behavioral patterns to recognise

Command
An action wrapped as an object: lets you queue, undo, retry. Used in CQRS write models and editor undo stacks.
State
An object's behaviour changes based on a state field. Useful when an if chain becomes a switch becomes a five-way nest.
Iterator
Walk through a collection without exposing its shape. Languages give you this for free now.
Template method
Define a skeleton with override hooks. Common in inheritance hierarchies — used less in modern composition-first code.

In practice

You're not going to look up "is this Strategy or Observer?" mid-implementation. But when you see ten classes that all wire callbacks the same way, naming it "Observer" lets you have a one-sentence conversation about it. That's the value of the vocabulary.

Key takeaways

  • Observer is pub/sub at the object level — always have an unsubscribe story.
  • Strategy swaps an algorithm; in TypeScript a plain function often suffices.
  • Chain of Responsibility is the engine inside every middleware stack.
  • Command, State, Iterator show up less but explain a lot of legacy code.
  • The patterns are vocabulary for the conversations, not blueprints for new code.

Checkpoint questions

Use these to test whether the lesson is clear enough to explain without rereading.

  1. 1When does Observer make sense, and what cleanup problem can it create?
  2. 2How can Strategy be represented as a simple function in TypeScript?
  3. 3What makes Chain of Responsibility useful for request pipelines?
  4. 4Which behavioral pattern would you choose for runtime algorithm selection?

References

External resources for going deeper after the lesson above.