Basic Docs
patternsarchitecture

Essential Design Patterns

Factory, Observer, and Strategy patterns in JavaScript/TypeScript.

Creational: Factory Pattern

The Factory pattern abstracts object creation behind a single function or class. Callers request an instance by type without coupling themselves to concrete constructors — making it easy to swap implementations later.

Client calls createButton(type)
Factory resolves type
Returns concrete instance
factory.ts
// Factory Pattern
interface Button { render(): string }

class PrimaryButton implements Button {
  render() { return "<button class='primary'>Click</button>" }
}
class IconButton implements Button {
  render() { return "<button class='icon'>★</button>" }
}

function createButton(type: "primary" | "icon"): Button {
  const map = { primary: PrimaryButton, icon: IconButton }
  return new map[type]()
}

const btn = createButton("primary")
btn.render() // → <button class='primary'>Click</button>

Behavioral: Observer Pattern

The Observer pattern lets a subject notify many subscribers when its state changes. It is the foundation of event systems, reactive stores, and real-time UIs. Each subscriber can also unsubscribe independently.

Subject
emit(data)
Observer 1
Observer 2
Observer Message Flow
1
State Changes
Subject state mutates
2
emit(data)
Notifies all listeners
3
Observer 1
Receives notification
4
Observer 2
Receives notification
5
Unsubscribe
Listeners detach cleanly
observer.ts
// Observer Pattern
type Listener<T> = (data: T) => void

class EventEmitter<T> {
  private listeners: Listener<T>[] = []

  subscribe(fn: Listener<T>) {
    this.listeners.push(fn)
    // returns an unsubscribe function
    return () => { this.listeners = this.listeners.filter(l => l !== fn) }
  }

  emit(data: T) {
    this.listeners.forEach(fn => fn(data))
  }
}

const counter = new EventEmitter<number>()
const unsub = counter.subscribe(v => console.log("Observer 1:", v))
counter.subscribe(v => console.log("Observer 2:", v))
counter.emit(42) // both observers fire
unsub()          // Observer 1 is now unsubscribed

Structural: Strategy Pattern

The Strategy pattern defines a family of interchangeable algorithms behind a common interface. You swap behavior at runtime without touching the host class — eliminating chains of if/else or switch statements.

Interchangeable Strategies
ascending: SortStrategy
descending: SortStrategy
mergeSort: SortStrategy
strategy.ts
// Strategy Pattern
interface SortStrategy {
  sort(data: number[]): number[]
}

const ascending: SortStrategy = {
  sort: (data) => [...data].sort((a, b) => a - b),
}
const descending: SortStrategy = {
  sort: (data) => [...data].sort((a, b) => b - a),
}

class Sorter {
  constructor(private strategy: SortStrategy) {}

  setStrategy(s: SortStrategy) { this.strategy = s }
  sort(data: number[]) { return this.strategy.sort(data) }
}

const sorter = new Sorter(ascending)
sorter.sort([3, 1, 4, 1, 5])    // [1, 1, 3, 4, 5]
sorter.setStrategy(descending)   // swap at runtime
sorter.sort([3, 1, 4, 1, 5])    // [5, 4, 3, 1, 1]

When to Use What

PatternUse Case
FactoryMultiple object types sharing one interface — UI components, parsers, adapters
ObserverOne-to-many state updates — event buses, reactive stores, WebSocket feeds
StrategySwappable algorithms — sorting, validation rules, payment processors
When to Apply Each Pattern
Simple app
Start without patterns — plain functions and objects
Multiple object types
Reach for Factory to unify creation logic
Growing event complexity
Observer decouples state updates from consumers
Diverging algorithms
Strategy swaps behavior without branching

Anti-patterns to Avoid

!God Object
A class with 20+ unrelated methods becomes untestable and fragile. Split responsibilities across focused classes instead of accumulating them in one place.
!Premature Abstraction
Do not apply patterns speculatively. Add a Factory only when you actually have two or more concrete types to manage. Add Observer only when real subscribers exist.
!Pattern Overuse
Every pattern adds a layer of indirection. If the abstraction does not simplify your code or tests, it is adding complexity, not reducing it. Start simple and refactor toward patterns only when the need is clear.
Built: 4/8/2026, 12:01:11 PM