Behavioral Design Patterns
Behavioral patterns define how objects communicate and distribute responsibility. They govern algorithms, control flow, and the assignment of duties between objects — without coupling concrete implementations together. Related: Creational Design Patterns, Structural Design Patterns, Software Design PrinciplesAgent Trigger
Apply when: Choosing how objects communicate or coordinate — Observer, Strategy, State, Command, Mediator, Chain of Responsibility, etc. Rule of thumb: Name the collaboration problem first; don’t reach for a pattern unless the variation it absorbs is real.Chain of Responsibility
Intent: Pass a request along a chain of handlers; each handler decides to process it or forward it. When to use:- Multiple handlers may process a request and the set isn’t known upfront (middleware pipelines, auth checks)
- Handlers must be composable and orderable at runtime
- You want to avoid coupling the sender to a specific receiver
- Every request must be guaranteed to be handled (CoR may silently drop unhandled requests)
- Chain is static and rarely changes — a simple
if/elseor strategy is cleaner
- Building a chain so long that debugging which handler acted becomes hard — prefer logging at each node
- Using CoR when all requests need a response; prefer Strategy or explicit dispatch instead
Command
Intent: Encapsulate a request as a standalone object, enabling queuing, undo/redo, and deferred execution. When to use:- You need undo/redo (text editors, drawing tools)
- Operations must be queued, logged, or scheduled (job queues, audit trails)
- Multiple UI elements (button, shortcut, menu) trigger the same action
- Simple one-shot operations with no need for history or deferral — adds unnecessary indirection
- The operation has no meaningful inverse (undo is impossible anyway)
- Putting business logic inside the Command itself — Command should delegate to a Receiver
- Conflating Command with Strategy: Command is about what happened (history, undo); Strategy is about how to do something now
Iterator
Intent: Provide a uniform interface to traverse a collection without exposing its internal structure. When to use:- Collection has a complex internal structure (tree, graph) clients shouldn’t care about
- Multiple simultaneous traversals of the same collection are needed
- You want to swap traversal algorithms without changing client code
- Simple arrays/lists where the language’s built-in
for...ofsuffices — unnecessary abstraction - Performance-critical traversal of specialized structures where direct access is faster
- Modifying the collection during iteration (undefined behavior in most implementations)
- Making the iterator stateful in the collection class itself — kills parallel traversal
Mediator
Intent: Centralize communication between objects through a single mediator, eliminating direct dependencies between components. When to use:- Many components interact in complex ways (chat rooms, form validation, UI control coordination)
- Reusing a component requires dragging in too many dependencies
- You want to change how components collaborate without touching them
- Only two or three objects need to coordinate — a mediator is overkill, direct reference is fine
- The “mediator” ends up being a God Object that knows everything and does everything
- Letting the mediator grow into a God Object — split mediators by bounded context
- Using Mediator when Observer would do (see comparison table below)
Memento
Intent: Capture an object’s internal state as an opaque snapshot, enabling rollback without breaking encapsulation. When to use:- Undo/redo requires full state restoration (editors, transactions)
- Direct state access would violate encapsulation (private fields must be saved)
- Rollback on error in multi-step operations
- State is large and snapshots are frequent — RAM consumption becomes prohibitive
- The object’s state is easily reconstructible from a command inverse (use Command undo instead)
- Exposing Snapshot fields publicly — caretakers should only see opaque snapshots (metadata only)
- Saving snapshots too frequently without a cap strategy (LRU limit, max history depth)
Observer
Intent: Define a one-to-many dependency so that when one object changes state, all dependents are notified automatically. When to use:- State changes in one object need to trigger updates in unknown/dynamic sets of others
- You want loose coupling between publisher and subscriber (different modules, plugins)
- Subscribers should be addable/removable at runtime
- Notification order matters and must be guaranteed — Observer fires in registration order by default, which is fragile
- Notification chains are deep (A notifies B which notifies C…) — debugging becomes hard, use Mediator instead
- Forgetting to unsubscribe — classic memory leak in long-lived UIs
- Publishing too many fine-grained events — subscribers become overwhelmed; batch or coarsen events
State
Intent: Allow an object to change its behavior when its internal state changes, by delegating to a state object rather than branching on a state field. When to use:- Object behavior changes dramatically with state and the state-specific code grows complex
- State transitions are frequent and involve multiple methods
- You have a finite-state machine with many states and the
if/switchsprawl is growing
- Only a few states with simple, rarely-changing transitions — a plain enum + switch is easier to follow
- States don’t actually change behavior, just data — State pattern adds class overhead for no gain
- Letting states depend on each other heavily — states should transition the context, not call sibling state methods directly
- Using State when Strategy would do (see comparison table below)
Strategy
Intent: Define a family of interchangeable algorithms, encapsulate each one, and let clients select or swap them at runtime. When to use:- Multiple variants of an algorithm exist and the right one is chosen at runtime (sorting, routing, pricing)
- You want to eliminate
if/switchblocks that select algorithm variants - Algorithms should be testable in isolation without touching the context
- Only one or two variants exist and they’re unlikely to grow — a simple function is cleaner
- Clients can’t know which strategy to choose — Strategy requires the caller to understand the differences
execute(skillName, context).
Anti-patterns:
- Making Strategy objects stateful — they should be pure transformers; state belongs in the context
- Exposing too many strategy variants to clients — use a factory or registry to hide selection logic
Template Method
Intent: Define the skeleton of an algorithm in a base class, deferring specific steps to subclasses — without allowing subclasses to change the overall structure. When to use:- Multiple classes share the same algorithm structure but differ in specific steps (data parsers, report generators)
- You want to enforce a fixed processing order while allowing customization of individual steps
- Reducing duplication across nearly-identical subclasses
- Composition is preferred over inheritance — use Strategy instead (runtime swap, no subclassing required)
- The algorithm has many steps and subclasses need to skip or reorder them — Template Method is inflexible here
- Making the template method overridable — subclasses must extend steps, not the skeleton itself
- Adding so many hooks that subclasses can override almost everything — at that point use Strategy
Visitor
Intent: Separate an operation from the object structure it operates on, enabling new operations without modifying element classes. When to use:- You need to add many distinct, unrelated operations to a stable class hierarchy (AST traversal, export formats)
- The element hierarchy is closed for modification but open for new behaviors
- Operations must accumulate state across multiple elements (e.g., collecting metrics while traversing a tree)
- The element hierarchy changes frequently — every new element type requires updating all visitors
- Elements have complex private state that visitors can’t access without breaking encapsulation
- Using Visitor when the element hierarchy is unstable — you’ll be updating every visitor on every new class
- Skipping the
acceptmethod and doinginstanceofdispatch instead — loses double-dispatch correctness
Domain Event
Intent: Represent something meaningful that happened in the domain as an immutable, named, timestamped value object; distribute it to interested parties. When to use:- You need a full audit log of what triggered state changes
- Multiple downstream systems or services must react to the same business fact
- Building toward Event Sourcing (Domain Events are a prerequisite)
- Cross-aggregate coordination in DDD without tight coupling
- Simple CRUD with no downstream consumers — event infrastructure adds overhead for no benefit
- The “event” is purely technical (e.g., a cache miss) rather than a domain-meaningful fact
- Mutable event objects — source data must be immutable (retroactive corrections are separate events)
- Publishing events before the transaction commits — subscribers react to facts that may roll back
- Unnamed, generic events (
DataChanged) — events should name what happened in domain language
Confusion Table
Commonly confused pairs:Cross-references
- Software Design Principles — Strategy, Observer, CoR all follow OCP; Iterator and Command follow SRP
- Creational Design Patterns — Command + Prototype for cloneable command history; Iterator + Factory Method for typed iterators
- Structural Design Patterns — CoR + Composite (bubble through parent tree); Visitor + Composite (traverse and operate)
- Agent Skills — Strategy pattern is the direct conceptual basis for the skill architecture in agent harnesses