
Implementation Design Patterns
- 81 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
implementation-design-patterns is a Claude Code skill in the AI & Agent Building category.
Key points
- implementation-design-patterns
- AI & Agent Building
- AI-coding skill
Implementation Design Patterns by the numbers
- 81 all-time installs (skills.sh)
- +7 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #5,179 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill implementation-design-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 81 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I helps with ai & agent building tasks during ai-assisted development?
Helps with ai & agent building tasks during AI-assisted development.
Who is it for?
Best when you're working on ai & agent building and need structured help with implementation-design-patterns.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks during ai-assisted development, or when implementation-design-patterns is a claude code skill in the ai & agent building category.
What you get
Structured output aligned to implementation-design-patterns: implementation-design-patterns; AI & Agent Building; AI-coding skill.
Files
TypeScript Design Patterns Best Practices (Refactoring Guru)
Implementation reference for the 22 Gang of Four design patterns in TypeScript, distilled from refactoring.guru. Each of the 22 pattern files across 3 categories captures intent, problem, solution, applicability, a runnable TypeScript example, implementation steps, pros/cons, and relations to sibling patterns.
The patterns are a vocabulary for structural decisions, not a prescription. Reach for a pattern only when its applicability criteria match the problem at hand — every pattern entry includes a When NOT to Use section to guard against over-engineering.
When to Apply
- Refactoring a class that has grown unwieldy via inheritance — combinatorial subclasses, conditional branching on type, or a "god class" with many responsibilities
- Designing a new module whose collaborators are not yet fixed — you want to keep the interface stable while implementations vary
- Integrating an incompatible third-party API, library, or legacy class into existing code
- Modeling a tree-shaped domain (file systems, organization charts, expression ASTs, UI component trees) where leaves and branches must be treated uniformly
- Adding cross-cutting behavior at runtime — logging, caching, access control, decoration — without subclassing
- Selecting an algorithm or behavior variant at runtime based on configuration, user input, or environmental conditions
- Implementing undo/redo, history snapshots, transactional rollback, or scheduling/queueing of operations
- Coordinating many objects whose direct mutual references have become tangled — a hub that brokers communication
- Notifying many subscribers when something changes — event systems, reactive data flows
- Reviewing code that smells like a pattern is implicit (large switch on
kind, parallel class hierarchies, identical algorithm skeletons across siblings) — make it explicit
Rule Categories
| # | Category | Impact | Patterns | When to reach for this group |
|---|---|---|---|---|
| 1 | Creational | HIGH | 5 | Object construction is non-trivial, varies by configuration, or risks tight coupling to concrete classes |
| 2 | Structural | HIGH | 7 | Composing classes/objects into larger structures while keeping parts substitutable |
| 3 | Behavioral | HIGH | 10 | Distributing responsibility and defining how objects collaborate at runtime |
How to Use
1. Recognize the shape. Read the Quick Reference below and identify which pattern's intent matches your problem. Most pattern-shaped problems sound like one of the listed phrases. 2. Read the pattern reference. Open references/{category}-{pattern}.md. Confirm intent, then read Applicability and When NOT to Use before adopting. 3. Adapt the example. The TypeScript example uses pedagogical names (ConcreteStrategyA, Receiver). Rename to domain terms before merging. 4. Check the relations. Each entry ends with Related Patterns — siblings worth considering for the same problem.
Quick Reference
1. Creational Patterns (object instantiation)
- `creational-factory-method` — Subclasses decide which concrete product to create. "I need to add new product types without touching the creator code." — HIGH
- `creational-abstract-factory` — Produce families of related objects together. "My code must work with multiple matching variants (chair+sofa+table) and shouldn't mix families." — MEDIUM-HIGH
- `creational-builder` — Construct complex objects step by step. "My constructor has 10+ parameters or I have a telescoping-constructor smell." — HIGH
- `creational-prototype` — Clone objects through their own
clone()method. "I need to copy objects without depending on their concrete class." — MEDIUM - `creational-singleton` — Guarantee a single shared instance with a global access point. "I need exactly one instance of this class — config, registry, pool." — MEDIUM
2. Structural Patterns (composition)
- `structural-adapter` — Translate one interface to another. "I need to use a library whose API doesn't match what my code expects." — HIGH
- `structural-bridge` — Split abstraction from implementation so they can vary independently. "I have two orthogonal dimensions and the subclass count is exploding." — MEDIUM
- `structural-composite` — Treat individual objects and compositions uniformly. "I have a tree (folders/files, groups/items, components/children) and want one interface for leaves and branches." — HIGH
- `structural-decorator` — Wrap an object to add behavior without subclassing. "I want to layer behaviors (logging + caching + auth) on the same interface at runtime." — HIGH
- `structural-facade` — Expose a simple interface over a complex subsystem. "My client code is tangled in initialization and orchestration of a third-party library." — HIGH
- `structural-flyweight` — Share common state across many objects to save memory. "I'm spawning millions of similar objects and running out of RAM." — LOW-MEDIUM
- `structural-proxy` — Substitute for another object to control access. "I need lazy loading, access control, caching, or logging without touching the real subject." — MEDIUM-HIGH
3. Behavioral Patterns (collaboration)
- `behavioral-chain-of-responsibility` — Pass a request along a chain of handlers. "I have a pipeline of validation / auth / parsing checks and want to add or reorder them dynamically." — MEDIUM-HIGH
- `behavioral-command` — Turn a request into a stand-alone object. "I need undo/redo, queueing, scheduling, macro recording, or to decouple invoker from receiver." — HIGH
- `behavioral-iterator` — Traverse a collection without exposing its representation. "I want clients to walk a structure without knowing if it's a list, tree, or graph." — HIGH
- `behavioral-mediator` — Centralize communication among components in a single hub. "My form fields all reference each other directly and the coupling is unmanageable." — MEDIUM
- `behavioral-memento` — Capture and restore an object's state without breaking encapsulation. "I need snapshots for undo/redo or transactional rollback." — LOW-MEDIUM
- `behavioral-observer` — Notify dependent objects when state changes. "Many objects need to react when one object changes — events, reactive UI, pub/sub." — CRITICAL
- `behavioral-state` — Alter behavior when internal state changes. "My class is a state machine with massive conditionals branching on a `status` field." — MEDIUM-HIGH
- `behavioral-strategy` — Make algorithms interchangeable at runtime. "I have multiple algorithms (sort, route, pay, compress) and want to pick one without conditionals." — HIGH
- `behavioral-template-method` — Fix an algorithm's skeleton in a base class; subclasses override steps. "Several classes share the same algorithm structure with minor step differences." — MEDIUM
- `behavioral-visitor` — Add operations to an object structure without modifying the classes. "I'd need to add 5 unrelated operations across an AST but I can't touch the node classes." — LOW-MEDIUM
How to Choose Between Similar Patterns
Several patterns share a structural shape but solve different problems. Read each pattern's Related Patterns section, then apply these distinctions:
- Adapter vs. Facade vs. Proxy vs. Decorator — all four wrap a target. Adapter changes the interface. Facade simplifies a subsystem. Proxy keeps the interface and controls access/lifecycle. Decorator keeps the interface and adds behavior recursively.
- Strategy vs. State — both swap a delegated object. Strategy objects are independent; the client picks one. State objects know each other and trigger transitions on the context.
- Strategy vs. Template Method — both vary parts of an algorithm. Strategy uses composition (swap at runtime). Template Method uses inheritance (fixed at compile time).
- Factory Method vs. Abstract Factory vs. Builder — Factory Method returns one product through a single method. Abstract Factory returns a family of related products through several methods. Builder assembles one complex product step by step.
- Composite vs. Decorator — both wrap children recursively. Composite sums or aggregates child results. Decorator adds responsibilities and passes through.
- Chain of Responsibility vs. Command vs. Mediator vs. Observer — all connect senders and receivers. CoR passes a request along a chain. Command makes the request a first-class object. Mediator centralizes mutual communication. Observer establishes one-publisher-to-many-subscribers notification.
Related Skills
- [`implementation-functional-patterns`](../implementation-functional-patterns/SKILL.md) — TypeScript's functional answer (HOFs, lambdas, pipelines, streams, composition) for problems where this catalog reaches for a class. Most Strategy / Iterator / Command / Chain-of-Responsibility / Decorator / Template-Method shapes have a lighter functional form in idiomatic TS; consult it before introducing a new class hierarchy.
References
1. Refactoring Guru — Design Patterns Catalog 2. Refactoring Guru — TypeScript Examples 3. Refactoring Guru — Creational Patterns 4. Refactoring Guru — Structural Patterns 5. Refactoring Guru — Behavioral Patterns
TypeScript Design Patterns
Version 0.1.0 Refactoring Guru May 2026
Note: This document is for agents and LLMs maintaining, generating, or refactoring TypeScript Design Patterns code — the 22 Gang of Four patterns in TypeScript. Humans may also find it useful, but guidance here is optimized for AI-assisted workflows.
---
Abstract
Implementation guide for the 22 Gang of Four design patterns with TypeScript examples, distilled from refactoring.guru. Each pattern reference covers intent, the problem it solves, the structural solution, applicability (when to use and when not to), a complete runnable TypeScript example with output, implementation steps, pros/cons, and relations to sibling patterns. Patterns are grouped by purpose: 5 Creational (Factory Method, Abstract Factory, Builder, Prototype, Singleton), 7 Structural (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy), and 10 Behavioral (Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor). Use this skill when you recognize a pattern-shaped problem — class explosion via inheritance, scattered conditionals branching on type, tight coupling between caller and concrete class, tree-shaped models, runtime algorithm selection, undo/redo, state-dependent behavior — and need a vetted structural recipe instead of inventing one.
---
Table of Contents
1. Creational Patterns — HIGH
- 1.1 Use Abstract Factory to Produce Families of Related Objects — MEDIUM-HIGH (prevents mixing incompatible variants (e.g., Victorian chair with Modern sofa) by guaranteeing all objects returned from one factory belong to the same family, eliminates parallel
if (style === ...)conditionals at every product-creation site) - 1.2 Use Builder to Construct Complex Objects Step by Step — HIGH (eliminates telescoping-constructor smell (constructors with 10+ parameters and many overloads), prevents subclass explosion for every parameter combination, allows the same construction sequence to produce different representations)
- 1.3 Use Factory Method to Decouple Object Creation from Concrete Classes — HIGH (eliminates direct
new ConcreteX()calls scattered through callers, isolates product instantiation so adding a new product type touches only one creator subclass instead of every call site) - 1.4 Use Prototype to Clone Objects Without Coupling to Concrete Classes — MEDIUM (enables copying complex pre-configured objects through a common
clone()interface, preserves access to private fields that external copy code cannot reach, removes the need for parallel "copy constructor" subclasses) - 1.5 Use Singleton to Guarantee a Single Shared Instance — MEDIUM (enforces exactly one instance of a shared resource (config, registry, connection pool, logger), prevents accidental duplicate instantiation that diverges state, and provides a single named access point that's easy to find and replace)
2. Structural Patterns — HIGH
- 2.1 Use Adapter to Make Incompatible Interfaces Cooperate — HIGH (enables reusing existing classes whose interface doesn't match what callers expect, eliminates ad-hoc conversion code scattered across call sites, isolates third-party API translation in one class)
- 2.2 Use Bridge to Split Abstraction from Implementation — MEDIUM (prevents exponential subclass explosion when a class varies along two or more independent dimensions, allows abstraction and implementation hierarchies to evolve separately, enables runtime swapping of implementations)
- 2.3 Use Composite to Treat Trees and Leaves Uniformly — HIGH (eliminates
instanceofand type-discrimination throughout traversal code, enables recursive operations across object trees through a single interface, lets clients work with arbitrarily nested structures without knowing the nesting level) - 2.4 Use Decorator to Attach Behaviors at Runtime via Wrappers — HIGH (reduces N×M subclass explosion (channels × combinations like EmailWithSmsWithSlack) to N small decorators, eliminates duplicated wrapping code, enables adding or removing responsibilities dynamically)
- 2.5 Use Facade to Hide a Complex Subsystem Behind One Interface — HIGH (replaces sprawling client code that orchestrates many subsystem objects with a single entry-point class, reduces coupling between application code and third-party library internals, eliminates duplicated initialization sequences across callers)
- 2.6 Use Flyweight to Share Common State Across Many Objects — LOW-MEDIUM (drastically reduces memory footprint when spawning millions of similar objects (game particles, cell sprites, text glyphs) by sharing immutable intrinsic state and passing variable extrinsic state per call)
- 2.7 Use Proxy to Insert a Substitute Controlling Access to an Object — MEDIUM-HIGH (enables lazy loading, access control, caching, and logging without modifying the real subject or duplicating that logic at every call site, preserves the original interface so callers remain unchanged)
3. Behavioral Patterns — HIGH
- 3.1 Use Chain of Responsibility to Pass Requests Through Handlers — MEDIUM-HIGH (replaces hardcoded validation/auth/parsing pipelines with composable handler chains, enables reordering or adding new handlers at runtime without modifying others, eliminates deeply nested if/else cascades that obscure pipeline intent)
- 3.2 Use Command to Turn Requests into Stand-Alone Objects — HIGH (enables undo/redo, queueing, scheduling, and macro recording by reifying requests as objects, decouples the invoker (button, shortcut, menu) from the receiver (business logic), eliminates duplicated invocation logic across UI surfaces)
- 3.3 Use Iterator to Traverse Collections Without Exposing Their Internals — HIGH (hides collection representation from callers (list, tree, graph, stream all look the same), enables multiple independent traversals over the same collection, eliminates duplicated traversal code throughout the application)
- 3.4 Use Mediator to Replace Many-to-Many Coupling with a Hub — MEDIUM (reduces N×N component dependencies to N×1 by routing all communication through a single mediator, makes components reusable in other contexts since they no longer reference each other directly)
- 3.5 Use Memento to Snapshot State Without Breaking Encapsulation — LOW-MEDIUM (captures restorable snapshots of an originator's state through a narrow interface so the caretaker (history, transaction log) can store them without ever seeing the private fields, preserves encapsulation that exposing getters would violate)
- 3.6 Use Observer to Broadcast State Changes to Many Subscribers — CRITICAL (enables one-to-many notification of state changes without the publisher knowing its subscribers — foundational to event systems, reactive UI frameworks, pub/sub, and dataflow programming)
- 3.7 Use State to Alter Behavior When Internal State Changes — MEDIUM-HIGH (replaces sprawling
switch(state)blocks in every method with polymorphic state objects, eliminates the bug-prone duplication of state checks across an object's methods, makes adding a new state a single new class instead of editing every method) - 3.8 Use Strategy to Make Algorithms Interchangeable at Runtime — HIGH (eliminates
if (type === 'a') ... else if (type === 'b')algorithm-selection conditionals scattered through business code, enables runtime swapping of algorithm variants, isolates each algorithm in its own class for independent testing and reuse) - 3.9 Use Template Method to Fix an Algorithm Skeleton and Let Subclasses Override Steps — MEDIUM (eliminates duplicated algorithm scaffolding across sibling classes by hoisting the shared sequence into a base class, lets subclasses override only the steps that legitimately vary, removes client conditionals that switch on subclass type)
- 3.10 Use Visitor to Add Operations to Class Hierarchies Without Modifying Them — LOW-MEDIUM (enables adding new operations (export, validate, render, optimize) across a closed object hierarchy by writing a new visitor class instead of editing every node type, isolates each new operation in one place rather than scattering it across the hierarchy)
---
References
1. https://refactoring.guru/design-patterns/catalog 2. https://refactoring.guru/design-patterns/typescript 3. https://refactoring.guru/design-patterns/creational-patterns 4. https://refactoring.guru/design-patterns/structural-patterns 5. https://refactoring.guru/design-patterns/behavioral-patterns
---
Source Files
This document was compiled from individual reference files. For detailed editing or extension:
| File | Description |
|---|---|
| references/_sections.md | Category definitions and impact ordering |
| SKILL.md | Quick reference entry point |
| metadata.json | Version and reference URLs |
Gotchas
Append entries as they're discovered. Format:
### {Short title of the failure mode}
{What went wrong, how to recognize it, and how to avoid it.}
Added: {YYYY-MM-DD}---
Don't paste pedagogical names into production code
The "Correct" code blocks in every pattern reference preserve the canonical refactoring.guru identifiers verbatim — ConcreteStrategyA, ConcreteStateB, Receiver, Adaptee, Component, Visitor. These names exist to make the structure legible against the GoF catalog; they communicate nothing about your domain. Before merging code derived from a pattern reference, rename every class/method to a domain term that explains the role at the call site (e.g., ConcreteStrategyA → AlphabeticalSort, Receiver → EmailService, Visitor → XmlExportVisitor). If a reviewer can't tell what the code does without referring back to the pattern, the renaming isn't done. Added: 2026-05-19
Watch for native-TypeScript shortcuts before reaching for the class-based template
Several patterns collapse into language features in modern TypeScript:
- Strategy →
type Strategy = (data: T[]) => T[]and pass the function directly - Iterator → implement
[Symbol.iterator]()sofor...ofworks - Observer →
EventTarget,EventEmitter, RxJSSubject, framework signals/hooks - Command → a closure:
const undo = () => { state = previous } - Singleton →
export const config = createConfig()at module scope (ESM caches the module) - Template Method → a higher-order function:
pipeline(parseStep, sharedRest) - Visitor → a discriminated union + exhaustive
switch (node.kind)
Recommend the class-based GoF template when the user actually needs the extra structure (subclassing, identity, dispatch). Recommend the native shortcut when they don't — over-engineering is more common than under-engineering with this catalog. Added: 2026-05-19
{
"version": "0.1.2",
"organization": "Refactoring Guru",
"technology": "TypeScript Design Patterns",
"discipline": "distillation",
"type": "library-reference",
"date": "May 2026",
"abstract": "Implementation guide for the 22 Gang of Four design patterns with TypeScript examples, distilled from refactoring.guru. Each pattern reference covers intent, the problem it solves, the structural solution, applicability (when to use and when not to), a runnable TypeScript example with output, implementation steps, pros/cons, and relations to sibling patterns. Patterns are grouped by purpose: 5 Creational (Factory Method, Abstract Factory, Builder, Prototype, Singleton), 7 Structural (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy), and 10 Behavioral (Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor). Use this skill when you recognize a pattern-shaped problem — class explosion via inheritance, scattered conditionals branching on type, tight coupling between caller and concrete class, tree-shaped models, runtime algorithm selection, undo/redo, state-dependent behavior — and need a vetted structural recipe.",
"references": [
"https://refactoring.guru/design-patterns/catalog",
"https://refactoring.guru/design-patterns/typescript",
"https://refactoring.guru/design-patterns/creational-patterns",
"https://refactoring.guru/design-patterns/structural-patterns",
"https://refactoring.guru/design-patterns/behavioral-patterns"
],
"category": "Design Patterns"
}
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group pattern references.
The 22 patterns are the original Gang of Four (GoF) catalog grouped by purpose: Creational (object instantiation), Structural (class/object composition), and Behavioral (object collaboration and responsibility assignment). All three categories are foundational — the impact label reflects the impact of applying the right pattern when the situation fits, not a global ranking between categories.
---
1. Creational Patterns (creational)
Impact: HIGH Description: Five patterns that decouple client code from the concrete classes it instantiates. Apply when object construction is non-trivial, varies by configuration, or risks tight coupling to specific classes — they isolate creation, enable polymorphic instantiation, and prevent constructor explosion.
2. Structural Patterns (structural)
Impact: HIGH Description: Seven patterns that compose classes and objects into larger structures while keeping the structure flexible and the parts substitutable. Apply when integrating incompatible APIs, building tree-shaped models, attaching responsibilities at runtime, hiding subsystem complexity, or controlling access to expensive resources.
3. Behavioral Patterns (behavioral)
Impact: HIGH Description: Ten patterns that distribute responsibility between objects and define how they communicate. Apply when behavior must vary at runtime, when responsibilities should pass through a sequence of handlers, when state changes must propagate to many listeners, or when an algorithm's skeleton should be fixed but specific steps vary by subclass.
Use Chain of Responsibility to Pass Requests Through Handlers
Pattern intent: pass a request along a chain of handlers. Each handler decides either to process the request or pass it to the next handler. Handlers stay independent and can be reordered or composed at runtime.
Shapes to recognize
- Sequential checks (auth → permissions → validation → rate limiting → cache) in one bloated function with early returns
- Middleware pipelines (Express/Koa-style
app.use(...)chains, ASP.NET pipelines) - Event bubbling: DOM events propagate up until something calls
stopPropagation() - Validation rules that must run in a particular order — and the order changes between contexts
Problem
An online ordering system requires sequential validation: authentication, permission verification, data sanitization, brute-force protection, caching. As checks accumulate, the code becomes bloated and hard to maintain. Logic scattered across one method can't be reused independently across other endpoints.
Solution
Extract each check into a standalone handler object with a single method. Link handlers into a chain where each holds a reference to the next. A request travels through the chain until a handler processes it or the chain is exhausted; any handler can stop propagation.
Incorrect (hardcoded pipeline with nested conditionals):
function handleOrder(request: OrderRequest) {
if (!authenticate(request)) return error('unauth');
if (!checkPermission(request)) return error('forbidden');
if (!validatePayload(request)) return error('bad request');
if (rateLimited(request)) return error('throttled');
if (cache.has(request.key)) return cache.get(request.key);
// Add a new check? Insert another branch. Reorder them? Risky.
return process(request);
}Correct (composable handler chain, runtime ordering):
/**
* The Handler interface declares a method for building the chain of handlers.
* It also declares a method for executing a request.
*/
interface Handler<Request = string, Result = string> {
setNext(handler: Handler<Request, Result>): Handler<Request, Result>;
handle(request: Request): Result;
}
/**
* The default chaining behavior can be implemented inside a base handler class.
*/
abstract class AbstractHandler implements Handler
{
private nextHandler?: Handler;
public setNext(handler: Handler): Handler {
this.nextHandler = handler;
// Returning a handler from here will let us link handlers in a
// convenient way like this:
// monkey.setNext(squirrel).setNext(dog);
return handler;
}
public handle(request: string): string {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return '';
}
}
/**
* All Concrete Handlers either handle a request or pass it to the next handler
* in the chain.
*/
class MonkeyHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'Banana') {
return `Monkey: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
class SquirrelHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'Nut') {
return `Squirrel: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
class DogHandler extends AbstractHandler {
public handle(request: string): string {
if (request === 'MeatBall') {
return `Dog: I'll eat the ${request}.`;
}
return super.handle(request);
}
}
/**
* The client code is usually suited to work with a single handler. In most
* cases, it is not even aware that the handler is part of a chain.
*/
function clientCode(handler: Handler) {
const foods = ['Nut', 'Banana', 'Cup of coffee'];
for (const food of foods) {
console.log(`Client: Who wants a ${food}?`);
const result = handler.handle(food);
if (result) {
console.log(` ${result}`);
} else {
console.log(` ${food} was left untouched.`);
}
}
}
const monkey = new MonkeyHandler();
const squirrel = new SquirrelHandler();
const dog = new DogHandler();
monkey.setNext(squirrel).setNext(dog);
console.log('Chain: Monkey > Squirrel > Dog\n');
clientCode(monkey);
console.log('');
console.log('Subchain: Squirrel > Dog\n');
clientCode(squirrel);Output:
Chain: Monkey > Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Monkey: I'll eat the Banana.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Subchain: Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
Banana was left untouched.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.When to use
- Process different request types in sequences known only at runtime
- Execute several handlers in a specific order that may change
- Add or reorder handler sets dynamically
When NOT to use
- The pipeline is fixed and small — a direct sequence of calls is clearer
- All handlers always run — a regular collection iteration without short-circuiting is simpler
- The pipeline state mutates and order coupling is high — Chain hides dependencies
Implementation Steps
1. Declare the handler interface with the request-handling method 2. Create an abstract base handler with a nextHandler reference and default forwarding behavior 3. Implement concrete handlers; each decides whether to process or forward 4. Assemble chains statically (init time) or dynamically (factory) 5. Allow requests to enter the chain at any position, not necessarily the head 6. Decide what happens when no handler processes a request
Pros
- Control the order of request handling
- Decouple invoker from receiver (Single Responsibility)
- Introduce new handlers without breaking existing code (Open/Closed)
Cons
- Requests may end up unhandled if no handler matches
Related Patterns
- Command — handlers can execute Commands, or the request itself can be a Command
- Composite — leaf components pass requests through parent chains to the root
- Decorator — same wrapping shape; decorators don't stop propagation while CoR handlers may
- Mediator / Observer — alternative coordination mechanisms; Mediator centralizes communication, Observer broadcasts to many subscribers
Reference: refactoring.guru/design-patterns/chain-of-responsibility
Use Command to Turn Requests into Stand-Alone Objects
Pattern intent: encapsulate a request as an object, letting you parameterize clients with different requests, queue or log requests, and support undoable operations. The invoker triggers a command; the command knows which receiver to invoke.
Shapes to recognize
- Same operation triggered from multiple UI surfaces (button, menu item, shortcut, drag handler) — each currently re-implements the logic
- Need to record, queue, schedule, or replay user actions
- Need to undo/redo operations
- Need a transaction log: "what just happened?"
- "I want to pass an action around as a value" — a closure handles this in JS, but Command adds undo, serialization, identity
Problem
GUI apps spawn button subclasses per action, coupling UI tightly to business logic. The same operation (copy/paste) must be invoked from toolbar buttons, context menus, and shortcuts — leading to duplicated logic.
Solution
Extract a request's details into a Command object with a single execute() method. Each invoker holds a Command and triggers it without knowing what runs. Commands delegate the real work to Receiver objects holding the business logic. The same Command can be reused across invokers; commands can be queued, logged, or undone.
Incorrect (UI elements re-implement business logic):
class CopyButton {
onClick(doc: Document) { /* same copy logic */ }
}
class CopyMenuItem {
onClick(doc: Document) { /* same copy logic */ }
}
class CopyShortcut {
onTrigger(doc: Document) { /* same copy logic */ }
}
// Add Cut? Three more classes. No history, no undo.Correct (request reified as Command):
/**
* The Command interface declares a method for executing a command.
*/
interface Command {
execute(): void;
}
/**
* Some commands can implement simple operations on their own.
*/
class SimpleCommand implements Command {
private payload: string;
constructor(payload: string) {
this.payload = payload;
}
public execute(): void {
console.log(`SimpleCommand: See, I can do simple things like printing (${this.payload})`);
}
}
/**
* However, some commands can delegate more complex operations to other objects,
* called "receivers."
*/
class ComplexCommand implements Command {
private receiver: Receiver;
private a: string;
private b: string;
/**
* Complex commands can accept one or several receiver objects along with
* any context data via the constructor.
*/
constructor(receiver: Receiver, a: string, b: string) {
this.receiver = receiver;
this.a = a;
this.b = b;
}
/**
* Commands can delegate to any methods of a receiver.
*/
public execute(): void {
console.log('ComplexCommand: Complex stuff should be done by a receiver object.');
this.receiver.doSomething(this.a);
this.receiver.doSomethingElse(this.b);
}
}
/**
* The Receiver classes contain some important business logic. They know how to
* perform all kinds of operations, associated with carrying out a request. In
* fact, any class may serve as a Receiver.
*/
class Receiver {
public doSomething(a: string): void {
console.log(`Receiver: Working on (${a}.)`);
}
public doSomethingElse(b: string): void {
console.log(`Receiver: Also working on (${b}.)`);
}
}
/**
* The Invoker is associated with one or several commands. It sends a request to
* the command.
*/
class Invoker {
private onStart?: Command;
private onFinish?: Command;
public setOnStart(command: Command): void {
this.onStart = command;
}
public setOnFinish(command: Command): void {
this.onFinish = command;
}
/**
* The Invoker does not depend on concrete command or receiver classes. The
* Invoker passes a request to a receiver indirectly, by executing a
* command.
*/
public doSomethingImportant(): void {
console.log('Invoker: Does anybody want something done before I begin?');
if (this.isCommand(this.onStart)) {
this.onStart.execute();
}
console.log('Invoker: ...doing something really important...');
console.log('Invoker: Does anybody want something done after I finish?');
if (this.isCommand(this.onFinish)) {
this.onFinish.execute();
}
}
private isCommand(object: Command | undefined): object is Command {
return object?.execute !== undefined;
}
}
const invoker = new Invoker();
invoker.setOnStart(new SimpleCommand('Say Hi!'));
const receiver = new Receiver();
invoker.setOnFinish(new ComplexCommand(receiver, 'Send email', 'Save report'));
invoker.doSomethingImportant();Output:
Invoker: Does anybody want something done before I begin?
SimpleCommand: See, I can do simple things like printing (Say Hi!)
Invoker: ...doing something really important...
Invoker: Does anybody want something done after I finish?
ComplexCommand: Complex stuff should be done by a receiver object.
Receiver: Working on (Send email.)
Receiver: Also working on (Save report.)When to use
- Parameterize objects with operations (callbacks with identity and state)
- Queue operations, schedule execution, or execute remotely
- Implement reversible (undo/redo) operations with a history stack
- Decouple senders (UI) from receivers (business logic)
When NOT to use
- A plain function or closure suffices — JavaScript functions are first-class
- No undo, no queueing, no logging — just pass a function
- A single short-lived call site — Command is overhead
Implementation Steps
1. Declare the Command interface with execute() 2. Extract each request into a concrete Command class implementing the interface 3. Identify sender (invoker) classes; add fields storing Commands 4. Change senders to execute Commands instead of calling business logic directly 5. Initialize in order: receivers → commands → senders
Pros
- Single Responsibility Principle: decouples invokers from performers
- Open/Closed Principle: introduce new commands without breaking existing code
- Implement undo/redo and deferred execution
- Assemble simple commands into composite ones
Cons
- Code complexity increases due to the additional indirection layer
Related Patterns
- Chain of Responsibility / Mediator / Observer — alternative ways to connect senders and receivers
- Memento — pair with Command to implement undo (snapshot before execute)
- Strategy — Strategy varies an algorithm; Command reifies a request
- Visitor — extends Command to operate across different object types
Reference: refactoring.guru/design-patterns/command
Use Iterator to Traverse Collections Without Exposing Their Internals
Pattern intent: traverse elements of a collection without exposing the underlying representation (list, stack, tree, graph). Iterators encapsulate traversal logic and state; multiple iterators can walk the same collection independently.
Shapes to recognize
- Two collections with different storage shapes — callers must know
array[i]vstree.walk()vsgraph.bfs() - Need to iterate the same collection multiple times in parallel, each from a different position
- Need different orders over the same data — forward, reverse, sorted, filtered
- "I want to support
for...ofover my custom collection"
Problem
Collections need sequential access regardless of internal structure. Adding traversal algorithms directly to collection classes obscures their primary responsibility and forces clients to know which collection they're dealing with.
Solution
Extract traversal behavior into separate iterator objects that hold the current position and walk the collection. Standard interfaces let clients consume any collection the same way. The collection exposes a factory method for iterators; the iterator does the rest.
Incorrect (caller knows about every collection's shape):
function printAll(words: WordsCollection) {
// Caller dives into internals — moving to a tree-backed store breaks this
for (let i = 0; i < words.items.length; i++) {
console.log(words.items[i]);
}
}Correct (collection returns an iterator; caller is structure-agnostic):
/**
* Iterator Design Pattern
*
* Intent: Lets you traverse elements of a collection without exposing its
* underlying representation (list, stack, tree, etc.).
*/
interface CollectionIterator<T> {
// Return the current element.
current(): T;
// Return the current element and move forward to next element.
next(): T;
// Return the key of the current element.
key(): number;
// Checks if current position is valid.
valid(): boolean;
// Rewind the Iterator to the first element.
rewind(): void;
}
interface Aggregator {
// Retrieve an external iterator.
getIterator(): CollectionIterator<string>;
}
/**
* Concrete Iterators implement various traversal algorithms. These classes
* store the current traversal position at all times.
*/
class AlphabeticalOrderIterator implements CollectionIterator<string> {
private collection: WordsCollection;
private position: number = 0;
private reverse: boolean = false;
constructor(collection: WordsCollection, reverse: boolean = false) {
this.collection = collection;
this.reverse = reverse;
if (reverse) {
this.position = collection.getCount() - 1;
}
}
public rewind() {
this.position = this.reverse ?
this.collection.getCount() - 1 :
0;
}
public current(): string {
return this.collection.getItems()[this.position];
}
public key(): number {
return this.position;
}
public next(): string {
const item = this.collection.getItems()[this.position];
this.position += this.reverse ? -1 : 1;
return item;
}
public valid(): boolean {
if (this.reverse) {
return this.position >= 0;
}
return this.position < this.collection.getCount();
}
}
/**
* Concrete Collections provide one or several methods for retrieving fresh
* iterator instances, compatible with the collection class.
*/
class WordsCollection implements Aggregator {
private items: string[] = [];
public getItems(): string[] {
return this.items;
}
public getCount(): number {
return this.items.length;
}
public addItem(item: string): void {
this.items.push(item);
}
public getIterator(): CollectionIterator<string> {
return new AlphabeticalOrderIterator(this);
}
public getReverseIterator(): CollectionIterator<string> {
return new AlphabeticalOrderIterator(this, true);
}
}
const collection = new WordsCollection();
collection.addItem('First');
collection.addItem('Second');
collection.addItem('Third');
const iterator = collection.getIterator();
console.log('Straight traversal:');
while (iterator.valid()) {
console.log(iterator.next());
}
console.log('');
console.log('Reverse traversal:');
const reverseIterator = collection.getReverseIterator();
while (reverseIterator.valid()) {
console.log(reverseIterator.next());
}Output:
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
FirstWhen to use
- Hide complexity of traversing complex data structures
- Reduce duplication of traversal code throughout the application
- Allow client code to traverse different data structures when types are unknown beforehand
- Enable parallel iteration with independent state
When NOT to use
- The collection is a plain array —
for...ofor.forEach()is built into the language - Only one traversal exists — exposing it as a method is simpler
- In modern TypeScript, implement
Symbol.iteratoron the collection rather than building a custom interface — it integrates withfor...of, spread, destructuring, and generators
Implementation Steps
1. Declare the iterator interface (next, valid, current, rewind) 2. Declare the collection interface with a method that returns an iterator 3. Implement concrete iterators bound to specific collection instances 4. Implement the collection to provide iterator factory methods 5. Replace explicit collection traversal in clients with iterator usage
Pros
- Single Responsibility: traversal extracted from collection
- Open/Closed: add new collections and iterators without changing client code
- Parallel iteration with independent state per iterator
- Iteration can be delayed and resumed
Cons
- May be excessive for simple collections
- Sometimes less efficient than direct element access for specialized collections
Related Patterns
- Composite — Iterators traverse Composite trees uniformly
- Factory Method — collection subclasses return iterators via a factory method
- Memento — capture and restore iteration state
- Visitor — execute operations on each element during iteration
Reference: refactoring.guru/design-patterns/iterator
Use Mediator to Replace Many-to-Many Coupling with a Hub
Pattern intent: reduce chaotic dependencies between objects by routing their communication through a mediator. Components don't reference each other — they notify the mediator, which decides what should happen next.
Shapes to recognize
- Form fields, dialog buttons, or UI components that import and directly invoke each other — N² couplings
- Components that can't be reused in another context because they're hardcoded to specific colleagues
- A god-controller that already exists informally — every component already calls it for everything
- "Changing field A breaks fields B and C because they listen to A directly"
Problem
As an application evolves, form-element interactions become complex. Components grow tightly coupled, making them hard to reuse in new contexts because they're interdependent on specific colleagues.
Solution
Components stop communicating directly. Each component holds a reference only to the Mediator and notifies it about events. The mediator decides which other components should react and invokes them. Components stay independent of each other.
Incorrect (every component imports every other component):
class CityField {
constructor(private countryField: CountryField, private zipField: ZipField) {}
onChange(city: string) {
this.countryField.update(city); // direct dependency
this.zipField.update(city); // direct dependency
}
}
// Adding a new field requires editing CityField (and every other field that should know).Correct (mediator routes traffic):
/**
* The Mediator interface declares a method used by components to notify the
* mediator about various events. The Mediator may react to these events and
* pass the execution to other components.
*/
interface Mediator {
notify(sender: object, event: string): void;
}
/**
* Concrete Mediators implement cooperative behavior by coordinating several
* components.
*/
class ConcreteMediator implements Mediator {
private component1: Component1;
private component2: Component2;
constructor(c1: Component1, c2: Component2) {
this.component1 = c1;
this.component1.setMediator(this);
this.component2 = c2;
this.component2.setMediator(this);
}
public notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:');
this.component2.doC();
}
if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:');
this.component1.doB();
this.component2.doC();
}
}
}
/**
* The Base Component provides the basic functionality of storing a mediator's
* instance inside component objects.
*/
class BaseComponent {
protected mediator: Mediator;
constructor(mediator?: Mediator) {
this.mediator = mediator!;
}
public setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}
/**
* Concrete Components implement various functionality. They don't depend on
* other components. They also don't depend on any concrete mediator classes.
*/
class Component1 extends BaseComponent {
public doA(): void {
console.log('Component 1 does A.');
this.mediator.notify(this, 'A');
}
public doB(): void {
console.log('Component 1 does B.');
this.mediator.notify(this, 'B');
}
}
class Component2 extends BaseComponent {
public doC(): void {
console.log('Component 2 does C.');
this.mediator.notify(this, 'C');
}
public doD(): void {
console.log('Component 2 does D.');
this.mediator.notify(this, 'D');
}
}
const c1 = new Component1();
const c2 = new Component2();
const mediator = new ConcreteMediator(c1, c2);
console.log('Client triggers operation A.');
c1.doA();
console.log('');
console.log('Client triggers operation D.');
c2.doD();Output:
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.When to use
- Classes are tightly coupled and difficult to modify in isolation
- Components can't be reused because they depend on too many colleagues
- You're creating many component subclasses just to swap collaborators in different contexts
When NOT to use
- The mediator already exists implicitly and is small — formalizing it adds ceremony
- Components only communicate one-to-one, predictably — direct references are simpler
- The mediator is becoming a god object — split into focused mediators or revisit the design
Implementation Steps
1. Identify tightly coupled classes that would benefit from independence 2. Declare the mediator interface describing the communication protocol 3. Implement the concrete mediator and have it hold references to components 4. Optionally make the mediator responsible for component creation/destruction 5. Components hold a reference to the mediator (typically via constructor) 6. Refactor components to notify the mediator instead of calling other components
Pros
- Centralizes communication (Single Responsibility)
- New mediators introduce new coordination logic without modifying components (Open/Closed)
- Reduces coupling between components
- Components become reusable
Cons
- Mediators can evolve into a god object over time
Related Patterns
- Chain of Responsibility / Command / Observer — alternative ways to connect senders and receivers
- Facade — similar shape, but Facade simplifies access to a subsystem without introducing new coordination logic, while Mediator centralizes mutual communication
- Observer — Mediator eliminates mutual dependencies via a hub; Observer establishes dynamic one-way connections; mediators sometimes use Observer internally
Reference: refactoring.guru/design-patterns/mediator
Use Memento to Snapshot State Without Breaking Encapsulation
Pattern intent: capture and externalize an object's state so it can be restored later, without revealing internal details. The originator produces mementos; a caretaker holds them; the originator alone reads from them.
Shapes to recognize
- Need to implement undo/redo and your object's state is private — you can't just copy fields from outside
- Transaction rollback: a "before" snapshot must be captured before a risky operation
- Editor history (text, drawing, spreadsheet) where each user action becomes a restorable point
- Adding public getters everywhere just so external code can copy the object — encapsulation slipping
Problem
A text editor needs undo, requiring state snapshots. Directly accessing private fields violates encapsulation; exposing every field via getters makes future refactors painful.
Solution
The originator object creates immutable snapshots (mementos) of its own state. Only the originator has full access to the memento's contents; the caretaker interacts through a narrow interface (timestamp, label) and can't see internal state.
Incorrect (encapsulation broken to support undo):
class Editor {
// Was private — now public so the history can copy it. Encapsulation gone.
public content: string = '';
public cursor: number = 0;
public selection: [number, number] | null = null;
}
class History {
private snapshots: { content: string; cursor: number; selection: [number, number] | null }[] = [];
save(editor: Editor) {
// History knows every internal field — adding a field forces editing History.
this.snapshots.push({
content: editor.content,
cursor: editor.cursor,
selection: editor.selection,
});
}
}Correct (originator produces opaque mementos; caretaker only stores them):
/**
* The Originator holds some important state that may change over time. It also
* defines a method for saving the state inside a memento and another method for
* restoring the state from it.
*/
class Originator {
private state: string;
constructor(state: string) {
this.state = state;
console.log(`Originator: My initial state is: ${state}`);
}
/**
* The Originator's business logic may affect its internal state. Therefore,
* the client should backup the state before launching methods of the
* business logic via the save() method.
*/
public doSomething(): void {
console.log('Originator: I\'m doing something important.');
this.state = this.generateRandomString(30);
console.log(`Originator: and my state has changed to: ${this.state}`);
}
private generateRandomString(length: number = 10): string {
const charSet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
return Array
.from({ length }, () => charSet.charAt(Math.floor(Math.random() * charSet.length)))
.join('');
}
/**
* Saves the current state inside a memento.
*/
public save(): Memento {
return new ConcreteMemento(this.state);
}
/**
* Restores the Originator's state from a memento object.
*/
public restore(memento: Memento): void {
this.state = memento.getState();
console.log(`Originator: My state has changed to: ${this.state}`);
}
}
/**
* The Memento interface provides a way to retrieve the memento's metadata, such
* as creation date or name. However, it doesn't expose the Originator's state.
*/
interface Memento {
getState(): string;
getName(): string;
getDate(): string;
}
/**
* The Concrete Memento contains the infrastructure for storing the Originator's
* state.
*/
class ConcreteMemento implements Memento {
private state: string;
private date: string;
constructor(state: string) {
this.state = state;
this.date = new Date().toISOString().slice(0, 19).replace('T', ' ');
}
/**
* The Originator uses this method when restoring its state.
*/
public getState(): string {
return this.state;
}
/**
* The rest of the methods are used by the Caretaker to display metadata.
*/
public getName(): string {
return `${this.date} / (${this.state.substr(0, 9)}...)`;
}
public getDate(): string {
return this.date;
}
}
/**
* The Caretaker doesn't depend on the Concrete Memento class. Therefore, it
* doesn't have access to the originator's state, stored inside the memento. It
* works with all mementos via the base Memento interface.
*/
class Caretaker {
private mementos: Memento[] = [];
private originator: Originator;
constructor(originator: Originator) {
this.originator = originator;
}
public backup(): void {
console.log('\nCaretaker: Saving Originator\'s state...');
this.mementos.push(this.originator.save());
}
public undo(): void {
if (!this.mementos.length) {
return;
}
const memento = this.mementos.pop();
if (!memento) {
return;
}
console.log(`Caretaker: Restoring state to: ${memento.getName()}`);
this.originator.restore(memento);
}
public showHistory(): void {
console.log('Caretaker: Here\'s the list of mementos:');
for (const memento of this.mementos) {
console.log(memento.getName());
}
}
}
const originator = new Originator('Super-duper-super-puper-super.');
const caretaker = new Caretaker(originator);
caretaker.backup();
originator.doSomething();
caretaker.backup();
originator.doSomething();
caretaker.backup();
originator.doSomething();
console.log('');
caretaker.showHistory();
console.log('\nClient: Now, let\'s rollback!\n');
caretaker.undo();
console.log('\nClient: Once more!\n');
caretaker.undo();Output (the random strings will differ between runs):
Originator: My initial state is: Super-duper-super-puper-super.
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: qXqxgTcLSCeLYdcgElOghOFhPGfMxo
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: iaVCJVryJwWwbipieensfodeMSWvUY
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: oSUxsOCiZEnohBMQEjwnPWJLGnwGmy
Caretaker: Here's the list of mementos:
2019-02-17 15:14:05 / (Super-dup...)
2019-02-17 15:14:05 / (qXqxgTcLS...)
2019-02-17 15:14:05 / (iaVCJVryJ...)
Client: Now, let's rollback!
Caretaker: Restoring state to: 2019-02-17 15:14:05 / (iaVCJVryJ...)
Originator: My state has changed to: iaVCJVryJwWwbipieensfodeMSWvUY
Client: Once more!
Caretaker: Restoring state to: 2019-02-17 15:14:05 / (qXqxgTcLS...)
Originator: My state has changed to: qXqxgTcLSCeLYdcgElOghOFhPGfMxoWhen to use
- Produce snapshots to restore previous object states
- Implement transaction rollback on errors
- Keep full copies of private fields separate from the object
- Direct field access would violate encapsulation
When NOT to use
- The object is immutable and small — copying it directly is simpler than introducing a memento
- Snapshots would consume too much memory and you can't bound history length
- A higher-level event-sourcing or CRDT approach already gives you replay/undo
Implementation Steps
1. Identify which class is the originator 2. Create a memento class mirroring the originator's relevant fields 3. Make the memento immutable — set state only via the constructor 4. Nest the memento inside the originator (or expose a narrow interface) so only the originator sees state 5. Add save() returning a memento and restore(memento) to the originator 6. Implement a caretaker that requests and stores mementos 7. Decide on memento lifecycle (history length, garbage collection)
Pros
- Produce snapshots without violating encapsulation
- Simplifies the originator by delegating history management to the caretaker
Cons
- Excessive mementos consume significant RAM
- Caretakers must track originator lifecycles to clean obsolete mementos
- Dynamic languages can't fully guarantee immutability inside mementos
Related Patterns
- Command — pair with Memento for undo: snapshot before execute, restore on undo
- Iterator — capture and rollback iteration state
- Prototype — simpler alternative for straightforward, mostly-public-state objects
Reference: refactoring.guru/design-patterns/memento
Use Observer to Broadcast State Changes to Many Subscribers
Pattern intent: define a one-to-many dependency between objects so when one (the subject) changes state, all dependents (observers) are notified automatically. Subscribers subscribe and unsubscribe at runtime.
Shapes to recognize
- Multiple parts of the system need to react when a value changes — UI re-render, log write, cache invalidation, analytics
- Polling: code that loops checking "did it change yet?" — replace with subscription
- DOM events, EventEmitter, RxJS Observables, React state, Vue reactivity, signals — all instances of Observer in different clothing
- The publisher must not know what reacts to its changes — loose coupling is required
Problem
Customers monitor product availability; visiting the store frequently wastes their time, and the store mass-emailing every customer spams uninterested ones. The system needs targeted notifications between independent parties.
Solution
Add a subscription mechanism to the publisher: an attach(observer) and detach(observer) API plus a notification method. When the publisher's state changes, it iterates subscribers and calls a common update(subject) method on each.
Incorrect (polling and tight coupling):
class Store {
public inStock: boolean = false;
}
// Every interested party polls the store on a timer — wasteful and laggy.
const store = new Store();
setInterval(() => {
if (store.inStock) sendEmail();
if (store.inStock) updateUI();
if (store.inStock) trackAnalytics();
}, 1000);Correct (publisher notifies subscribers on change):
/**
* The Subject interface declares a set of methods for managing subscribers.
*/
interface Subject {
// Attach an observer to the subject.
attach(observer: Observer): void;
// Detach an observer from the subject.
detach(observer: Observer): void;
// Notify all observers about an event.
notify(): void;
}
/**
* The Subject owns some important state and notifies observers when the state
* changes.
*/
class ConcreteSubject implements Subject {
public state!: number;
private observers: Observer[] = [];
public attach(observer: Observer): void {
const isExist = this.observers.includes(observer);
if (isExist) {
return console.log('Subject: Observer has been attached already.');
}
console.log('Subject: Attached an observer.');
this.observers.push(observer);
}
public detach(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer);
if (observerIndex === -1) {
return console.log('Subject: Nonexistent observer.');
}
this.observers.splice(observerIndex, 1);
console.log('Subject: Detached an observer.');
}
/**
* Trigger an update in each subscriber.
*/
public notify(): void {
console.log('Subject: Notifying observers...');
for (const observer of this.observers) {
observer.update(this);
}
}
/**
* Usually, the subscription logic is only a fraction of what a Subject can
* really do. Subjects commonly hold some important business logic, that
* triggers a notification method whenever something important is about to
* happen (or after it).
*/
public someBusinessLogic(): void {
console.log('\nSubject: I\'m doing something important.');
this.state = Math.floor(Math.random() * (10 + 1));
console.log(`Subject: My state has just changed to: ${this.state}`);
this.notify();
}
}
/**
* The Observer interface declares the update method, used by subjects.
*/
interface Observer {
update(subject: Subject): void;
}
/**
* Concrete Observers react to the updates issued by the Subject they had been
* attached to.
*/
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.state < 3) {
console.log('ConcreteObserverA: Reacted to the event.');
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && (subject.state === 0 || subject.state >= 2)) {
console.log('ConcreteObserverB: Reacted to the event.');
}
}
}
const subject = new ConcreteSubject();
const observer1 = new ConcreteObserverA();
subject.attach(observer1);
const observer2 = new ConcreteObserverB();
subject.attach(observer2);
subject.someBusinessLogic();
subject.someBusinessLogic();
subject.detach(observer2);
subject.someBusinessLogic();Output (the random `state` numbers differ per run):
Subject: Attached an observer.
Subject: Attached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 6
Subject: Notifying observers...
ConcreteObserverB: Reacted to the event.
Subject: I'm doing something important.
Subject: My state has just changed to: 1
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.
Subject: Detached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 5
Subject: Notifying observers...When to use
- One object's state changes require updating others whose set is unknown beforehand or changes dynamically
- GUI events where custom code hooks into widgets
- Cross-cutting reactions (logging, audit, analytics) that shouldn't pollute the publisher
When NOT to use
- The set of subscribers is fixed and tiny — direct calls are simpler
- You need ordered, synchronous, transactional updates — Observer fires in registration order without guarantees
- The publisher needs to coordinate (not just notify) — reach for Mediator instead
- TypeScript shortcut: an
EventEmitter,EventTarget, RxJSSubject, or a framework's reactive primitives often replaces a bespoke Observer
Implementation Steps
1. Separate the business logic into publisher (core state) and subscribers (reactions) 2. Declare the subscriber interface with at least an update method 3. Declare the publisher interface with attach/detach/notify 4. Implement subscription methods in the publisher (or in an abstract base) 5. Concrete publishers call notify() when important state changes 6. Implement concrete subscribers' update() methods 7. Client wires subscribers to publishers at startup
Pros
- Open/Closed Principle: add new subscriber types without modifying the publisher
- Establish dynamic relationships at runtime
Cons
- Subscribers are notified in registration order (or unspecified order in some implementations)
- Memory leaks if subscribers forget to unsubscribe
- Debugging cascading updates can be hard
Related Patterns
- Chain of Responsibility / Command / Mediator — alternative connection mechanisms
- Mediator — eliminates mutual dependencies via a hub; Observer establishes dynamic one-way connections; Mediator can be implemented using Observer internally
Reference: refactoring.guru/design-patterns/observer
Use State to Alter Behavior When Internal State Changes
Pattern intent: allow an object to alter its behavior when its internal state changes. The object will appear to change its class. Each possible state becomes a separate class implementing a common state interface; the context delegates state-dependent behavior to the current state object.
Shapes to recognize
- A class with several methods, each containing
switch (this.status) { ... }covering the same set of states - A workflow object (order, document, ticket) with status field and methods that act differently per status
- TCP connection, media player, finite state machine — anything where transitions matter
- "Adding a new state requires editing five methods"
Problem
An object behaves differently based on its internal state, and the number of states is substantial. Without the State pattern, this typically results in massive conditional statements scattered through methods. Each new state forces edits across every method that branches on state.
Solution
Create a separate class for each state. Move state-specific behavior into these classes. The context holds a reference to the current state object and delegates all state-dependent work to it. State objects can trigger transitions by handing the context a new state.
Incorrect (every method branches on the same status field):
class Document {
status: 'draft' | 'review' | 'published' = 'draft';
publish() {
switch (this.status) {
case 'draft': this.status = 'review'; break;
case 'review': this.status = 'published'; break;
case 'published': break;
}
}
reject() {
switch (this.status) {
case 'draft': /* noop */ break;
case 'review': this.status = 'draft'; break;
case 'published': /* can't reject */ break;
}
}
// Each method repeats the same switch — adding 'archived' touches all of them.
}Correct (state objects with transitions):
/**
* The Context defines the interface of interest to clients. It also maintains a
* reference to an instance of a State subclass, which represents the current
* state of the Context.
*/
class Context {
private state!: State;
constructor(state: State) {
this.transitionTo(state);
}
/**
* The Context allows changing the State object at runtime.
*/
public transitionTo(state: State): void {
console.log(`Context: Transition to ${(<any>state).constructor.name}.`);
this.state = state;
this.state.setContext(this);
}
/**
* The Context delegates part of its behavior to the current State object.
*/
public request1(): void {
this.state.handle1();
}
public request2(): void {
this.state.handle2();
}
}
/**
* The base State class declares methods that all Concrete State should
* implement and also provides a backreference to the Context object, associated
* with the State. This backreference can be used by States to transition the
* Context to another State.
*/
abstract class State {
protected context!: Context;
public setContext(context: Context) {
this.context = context;
}
public abstract handle1(): void;
public abstract handle2(): void;
}
/**
* Concrete States implement various behaviors, associated with a state of the
* Context.
*/
class ConcreteStateA extends State {
public handle1(): void {
console.log('ConcreteStateA handles request1.');
console.log('ConcreteStateA wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateB());
}
public handle2(): void {
console.log('ConcreteStateA handles request2.');
}
}
class ConcreteStateB extends State {
public handle1(): void {
console.log('ConcreteStateB handles request1.');
}
public handle2(): void {
console.log('ConcreteStateB handles request2.');
console.log('ConcreteStateB wants to change the state of the context.');
this.context.transitionTo(new ConcreteStateA());
}
}
const context = new Context(new ConcreteStateA());
context.request1();
context.request2();Output:
Context: Transition to ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA.When to use
- An object behaves differently based on internal state, and the number of states is substantial
- Classes contain massive conditionals that alter behavior based on a state field
- Significant duplicate code exists across similar states and transitions
When NOT to use
- Two or three states with trivial differences — a small switch suffices
- States rarely change and behavior overlap is high
- A pure data-driven state table is simpler than a class hierarchy for your case
Implementation Steps
1. Identify the context class needing state-dependent behavior 2. Declare a state interface with the relevant methods 3. Create concrete state classes implementing the interface 4. Add a state reference field and setter to the context 5. Replace conditional logic in context methods with calls to the state object 6. Implement transitions by instantiating and assigning new state objects (from inside states or from the context)
Pros
- Single Responsibility: state-specific code lives in one place per state
- Open/Closed: add new states without modifying existing ones
- Eliminates bulky conditionals from the context
Cons
- Can be overkill for simple machines with few states
Related Patterns
- Strategy — same composition shape; Strategy objects are independent and the client picks one. State objects know each other and trigger transitions on the context.
- Bridge — also composition-based, but Bridge splits abstraction from implementation along two orthogonal axes
- Memento — capture state snapshots for rollback alongside State
Reference: refactoring.guru/design-patterns/state
Use Strategy to Make Algorithms Interchangeable at Runtime
Pattern intent: define a family of algorithms, put each in a separate class, and make them interchangeable through a common interface. The context holds a reference to a strategy and delegates the algorithm execution to it.
Shapes to recognize
- A class with several methods that branch on
kind/type/modeto pick an algorithm - Multiple sort/route/format/pay/compress variants chosen at runtime by config or user choice
- A class growing massively because every algorithm change adds a branch
- "I want to swap how this works without recompiling or subclassing"
Problem
A navigation app initially supported car routes, then expanded to walking and public transit, with cyclist and tourist routes planned. Each algorithm addition doubled the main class's size, increased bug risk, and caused merge conflicts during team development.
Solution
Extract each algorithm variant into a separate class implementing a common Strategy interface. The context holds one strategy reference and delegates work to it. Clients pass the desired strategy in; switching algorithms is one assignment, not a code edit.
Incorrect (conditional algorithm selection inside the context):
class Navigator {
route(type: 'car' | 'walking' | 'transit', from: Loc, to: Loc) {
if (type === 'car') return /* car logic */ [];
else if (type === 'walking') return /* walking logic */ [];
else if (type === 'transit') return /* transit logic */ [];
// Add 'cyclist'? Edit this method (and every other one with the same shape).
}
}Correct (interchangeable strategy objects):
/**
* The Context defines the interface of interest to clients.
*/
class Context {
/**
* @type {Strategy} The Context maintains a reference to one of the Strategy
* objects. The Context does not know the concrete class of a strategy. It
* should work with all strategies via the Strategy interface.
*/
private strategy: Strategy;
/**
* Usually, the Context accepts a strategy through the constructor, but also
* provides a setter to change it at runtime.
*/
constructor(strategy: Strategy) {
this.strategy = strategy;
}
/**
* Usually, the Context allows replacing a Strategy object at runtime.
*/
public setStrategy(strategy: Strategy) {
this.strategy = strategy;
}
/**
* The Context delegates some work to the Strategy object instead of
* implementing multiple versions of the algorithm on its own.
*/
public doSomeBusinessLogic(): void {
console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
console.log(result.join(','));
}
}
/**
* The Strategy interface declares operations common to all supported versions
* of some algorithm.
*
* The Context uses this interface to call the algorithm defined by Concrete
* Strategies.
*/
interface Strategy {
doAlgorithm(data: string[]): string[];
}
/**
* Concrete Strategies implement the algorithm while following the base Strategy
* interface. The interface makes them interchangeable in the Context.
*/
class ConcreteStrategyA implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.sort();
}
}
class ConcreteStrategyB implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.reverse();
}
}
/**
* The client code picks a concrete strategy and passes it to the context. The
* client should be aware of the differences between strategies in order to make
* the right choice.
*/
const context = new Context(new ConcreteStrategyA());
console.log('Client: Strategy is set to normal sorting.');
context.doSomeBusinessLogic();
console.log('');
console.log('Client: Strategy is set to reverse sorting.');
context.setStrategy(new ConcreteStrategyB());
context.doSomeBusinessLogic();Output:
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,aWhen to use
- You need different variants of an algorithm within an object with runtime switching
- You have similar classes that differ only in their execution behavior
- You want to separate business logic from algorithm implementation details
- A class contains massive conditionals that pick an algorithm variant
When NOT to use
- The algorithm is small, stable, and rarely changes — a function suffices
- Algorithms must be selected at compile time and never swap at runtime — Template Method with inheritance is enough
- In modern TypeScript, the strategy interface is often just a function type:
type Strategy = (data: string[]) => string[]. Pass it directly without wrapping in a class.
Implementation Steps
1. Identify the algorithm prone to frequent change in the context 2. Declare a strategy interface common to all algorithm variants 3. Extract each algorithm into a class implementing the interface 4. Add a field storing a strategy reference and a setter on the context 5. Clients associate the context with a suitable strategy
Pros
- Swap algorithms at runtime
- Isolate algorithm implementation details from usage code
- Replace inheritance with composition
- Open/Closed Principle compliance for adding new strategies
Cons
- Unnecessary complexity for few, stable algorithms
- Clients must understand strategy differences to choose the right one
- In functional languages or modern TS, anonymous functions provide a simpler alternative
Related Patterns
- State — same composition shape; State objects know each other and trigger transitions on the context; Strategy objects are independent
- Bridge — also composition-based, but Bridge splits abstraction from implementation along two orthogonal axes
- Template Method — varies parts of an algorithm via inheritance (compile-time); Strategy varies the whole algorithm via composition (runtime)
- Decorator — Decorator changes appearance/behavior layer; Strategy changes the algorithm
Reference: refactoring.guru/design-patterns/strategy
Use Template Method to Fix an Algorithm Skeleton and Let Subclasses Override Steps
Pattern intent: define an algorithm's skeleton in a base class, allowing subclasses to override individual steps without changing the overall structure. Base class fixes "what" and "in what order"; subclasses fix "how" for the variable steps.
Shapes to recognize
- Two or three classes whose top-level method looks nearly identical except for 1-2 inner steps
- A processing pipeline (parse → analyze → format → output) where parsing varies by format but the rest is shared
- Test fixtures with setUp/tearDown and a body — JUnit's
TestCaseis a Template Method - "I keep copy-pasting this method and changing two lines"
Problem
A data-mining app processes documents in PDF, DOC, and CSV formats — three classes with nearly identical processing code differing only in parsing logic. Client code adds conditionals to handle each type. Duplication accumulates as the algorithm evolves.
Solution
Break the algorithm into discrete steps. Place the steps in a templateMethod() on a base class — the skeleton — and declare some steps abstract (subclasses must implement) and others virtual with defaults (subclasses may override). Hooks at strategic points let subclasses inject behavior without changing the algorithm itself.
Incorrect (duplicated algorithm across siblings):
class PdfMiner {
mine(file: string) {
const raw = readFile(file);
const text = parsePdf(raw); // varies
const data = analyze(text); // shared
const report = format(data); // shared
return writeReport(report); // shared
}
}
class CsvMiner {
mine(file: string) {
const raw = readFile(file);
const text = parseCsv(raw); // varies
const data = analyze(text); // duplicated
const report = format(data); // duplicated
return writeReport(report); // duplicated
}
}
// Add DocMiner? Same duplication grows. Fix a bug in analyze? Edit it everywhere.Correct (skeleton in base class; subclasses fill in the variable steps):
/**
* The Abstract Class defines a template method that contains a skeleton of some
* algorithm, composed of calls to (usually) abstract primitive operations.
*
* Concrete subclasses should implement these operations, but leave the template
* method itself intact.
*/
abstract class AbstractClass {
/**
* The template method defines the skeleton of an algorithm.
*/
public templateMethod(): void {
this.baseOperation1();
this.requiredOperations1();
this.baseOperation2();
this.hook1();
this.requiredOperation2();
this.baseOperation3();
this.hook2();
}
/**
* These operations already have implementations.
*/
protected baseOperation1(): void {
console.log('AbstractClass says: I am doing the bulk of the work');
}
protected baseOperation2(): void {
console.log('AbstractClass says: But I let subclasses override some operations');
}
protected baseOperation3(): void {
console.log('AbstractClass says: But I am doing the bulk of the work anyway');
}
/**
* These operations have to be implemented in subclasses.
*/
protected abstract requiredOperations1(): void;
protected abstract requiredOperation2(): void;
/**
* These are "hooks." Subclasses may override them, but it's not mandatory
* since the hooks already have default (but empty) implementation. Hooks
* provide additional extension points in some crucial places of the
* algorithm.
*/
protected hook1(): void { }
protected hook2(): void { }
}
/**
* Concrete classes have to implement all abstract operations of the base class.
* They can also override some operations with a default implementation.
*/
class ConcreteClass1 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass1 says: Implemented Operation1');
}
protected requiredOperation2(): void {
console.log('ConcreteClass1 says: Implemented Operation2');
}
}
/**
* Usually, concrete classes override only a fraction of base class' operations.
*/
class ConcreteClass2 extends AbstractClass {
protected requiredOperations1(): void {
console.log('ConcreteClass2 says: Implemented Operation1');
}
protected requiredOperation2(): void {
console.log('ConcreteClass2 says: Implemented Operation2');
}
protected hook1(): void {
console.log('ConcreteClass2 says: Overridden Hook1');
}
}
/**
* The client code calls the template method to execute the algorithm. Client
* code does not have to know the concrete class of an object it works with, as
* long as it works with objects through the interface of their base class.
*/
function clientCode(abstractClass: AbstractClass) {
abstractClass.templateMethod();
}
console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass1());
console.log('');
console.log('Same client code can work with different subclasses:');
clientCode(new ConcreteClass2());Output:
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anywayWhen to use
- Extend particular algorithm steps without modifying the whole algorithm
- Several classes have nearly identical algorithms with minor differences
- Eliminate conditionals in client code by leaning on polymorphism
- A shared algorithm structure must span multiple implementations
When NOT to use
- The algorithm varies fundamentally between subclasses — composition (Strategy) suits better
- Subclasses cannot meaningfully override the steps without violating Liskov substitution
- Only one variant exists — Template Method is overhead
Implementation Steps
1. Analyze the target algorithm for discrete steps; identify common vs unique steps 2. Create an abstract base class with the template method and abstract methods for the variable steps; consider marking the template method final-equivalent (TypeScript lacks final, use convention) 3. Provide default implementations where reasonable; leave others abstract 4. Add hooks between crucial steps as optional extension points 5. Create concrete subclasses implementing all abstract steps
Pros
- Clients override only specific parts; less affected by changes elsewhere
- Duplicate code consolidates into the superclass
Cons
- Some clients limited by the algorithm skeleton
- May violate Liskov Substitution if a subclass suppresses default step implementations
- Maintenance difficulty rises with more steps
Related Patterns
- Factory Method — specialization of Template Method (one step is a factory)
- Strategy — Strategy uses composition (swap at runtime); Template Method uses inheritance (compile-time)
- Decorator — alternative when extension must happen at runtime per-instance
Use Visitor to Add Operations to Class Hierarchies Without Modifying Them
Pattern intent: separate algorithms from the objects they operate on. New operations across a stable class hierarchy go into a Visitor; each element accepts the visitor and dispatches to the matching visit method. Double dispatch lets the visitor know the exact element type without instanceof.
Shapes to recognize
- An AST, geometric scene graph, or document tree with many node types
- New operations needed across the hierarchy (export to XML, type-check, render, optimize) — and you can't touch the node classes
- Each new operation currently means adding a method to every node class — N×M growth
- "I need 5 unrelated operations across 20 node types"
Problem
A production system with geographic data nodes needs XML export, but the architect prohibits changing existing node classes (stability concerns, single-responsibility violation if every operation lives on every node).
Solution
Place each new behavior in a separate Visitor class. Each element class implements accept(visitor) that calls back into visitor.visitConcreteX(this). The visitor exposes one method per concrete element type — double dispatch routes the call to the right pair (visitor type × element type) without instanceof.
Incorrect (every new operation touches every node class):
class Building { exportXml() { /* ... */ } typeCheck() { /* ... */ } optimize() { /* ... */ } }
class Road { exportXml() { /* ... */ } typeCheck() { /* ... */ } optimize() { /* ... */ } }
class Park { exportXml() { /* ... */ } typeCheck() { /* ... */ } optimize() { /* ... */ } }
// Add `renderForVR`? Edit every class. The hierarchy can't stay closed.Correct (operation lives in a visitor; nodes accept it):
/**
* The Component interface declares an `accept` method that should take the base
* visitor interface as an argument.
*/
interface Component {
accept(visitor: Visitor): void;
}
/**
* Each Concrete Component must implement the `accept` method in such a way that
* it calls the visitor's method corresponding to the component's class.
*/
class ConcreteComponentA implements Component {
/**
* Note that we're calling `visitConcreteComponentA`, which matches the
* current class name. This way we let the visitor know the class of the
* component it works with.
*/
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentA(this);
}
/**
* Concrete Components may have special methods that don't exist in their
* base class or interface. The Visitor is still able to use these methods
* since it's aware of the component's concrete class.
*/
public exclusiveMethodOfConcreteComponentA(): string {
return 'A';
}
}
class ConcreteComponentB implements Component {
/**
* Same here: visitConcreteComponentB => ConcreteComponentB
*/
public accept(visitor: Visitor): void {
visitor.visitConcreteComponentB(this);
}
public specialMethodOfConcreteComponentB(): string {
return 'B';
}
}
/**
* The Visitor Interface declares a set of visiting methods that correspond to
* component classes. The signature of a visiting method allows the visitor to
* identify the exact class of the component that it's dealing with.
*/
interface Visitor {
visitConcreteComponentA(element: ConcreteComponentA): void;
visitConcreteComponentB(element: ConcreteComponentB): void;
}
/**
* Concrete Visitors implement several versions of the same algorithm, which can
* work with all concrete component classes.
*/
class ConcreteVisitor1 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor1`);
}
public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor1`);
}
}
class ConcreteVisitor2 implements Visitor {
public visitConcreteComponentA(element: ConcreteComponentA): void {
console.log(`${element.exclusiveMethodOfConcreteComponentA()} + ConcreteVisitor2`);
}
public visitConcreteComponentB(element: ConcreteComponentB): void {
console.log(`${element.specialMethodOfConcreteComponentB()} + ConcreteVisitor2`);
}
}
/**
* The client code can run visitor operations over any set of elements without
* figuring out their concrete classes. The accept operation directs a call to
* the appropriate operation in the visitor object.
*/
function clientCode(components: Component[], visitor: Visitor) {
for (const component of components) {
component.accept(visitor);
}
}
const components = [
new ConcreteComponentA(),
new ConcreteComponentB(),
];
console.log('The client code works with all visitors via the base Visitor interface:');
const visitor1 = new ConcreteVisitor1();
clientCode(components, visitor1);
console.log('');
console.log('It allows the same client code to work with different types of visitors:');
const visitor2 = new ConcreteVisitor2();
clientCode(components, visitor2);Output:
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2When to use
- Perform many unrelated operations on all elements of a complex object structure (object trees, ASTs)
- Extract auxiliary behaviors and clean up business logic in primary classes
- A behavior makes sense only in some classes of a hierarchy, not all
When NOT to use
- The element hierarchy is unstable — every new element type forces updating every visitor
- The hierarchy is small and only one operation exists across it
- Visitors require access to private fields they shouldn't see — Visitor exposes shape and breaks encapsulation
- TypeScript discriminated unions + a
switch(node.kind)exhaustive check often replaces Visitor more clearly for AST-like structures
Implementation Steps
1. Declare the visitor interface with one visitX method per concrete element class 2. Add accept(visitor) to the element base interface 3. Implement accept in each concrete element, redirecting to the visitor's matching method (double dispatch) 4. Element classes work with visitors only through the visitor interface 5. Create concrete visitor classes, one per new operation 6. Pass visitors to elements via their accept methods
Pros
- Open/Closed Principle: introduce new operations without changing existing classes
- Single Responsibility: each operation lives in one visitor class
- Visitors can accumulate state while traversing the structure
Cons
- Adding or removing an element class forces updating every visitor
- Visitors may lack access to private fields and methods of elements
Related Patterns
- Command — Visitor extends Command-like dispatch to operate across different object classes via double dispatch
- Composite — pairs effectively with Visitor for traversing object trees
- Iterator — traverse the structure with an Iterator while applying a Visitor at each element
Reference: refactoring.guru/design-patterns/visitor
Use Abstract Factory to Produce Families of Related Objects
Pattern intent: an interface for creating families of related or dependent objects without specifying their concrete classes. Each concrete factory returns objects from a single variant; clients work with abstract products and never know which variant is active.
Shapes to recognize
- Code that branches on a style/theme/variant for every product it creates:
if (style === 'modern') new ModernChair(); else new VictorianChair();repeated for chair, sofa, table - Risk of accidentally pairing incompatible variants (a Victorian chair next to a Modern sofa)
- Cross-platform UI code where button + checkbox + dialog must all match the host OS
- "I have N parallel hierarchies that must vary together"
Problem
You produce furniture in variants (Modern, Victorian, ArtDeco) and need chair+sofa+table from the same family. Adding a new variant shouldn't require editing every product-creation site, and clients must not mix families.
Solution
Declare an abstract product interface per product type (Chair, Sofa, Table). Declare an Abstract Factory interface with one creation method per product type. Each concrete factory corresponds to one variant and returns matching products. Clients hold a reference to the abstract factory and never name a concrete class.
Incorrect (parallel conditionals risk mixing families):
class FurnitureShop {
buildLivingRoom(style: 'modern' | 'victorian') {
// Each call site duplicates the style branch — if a new style ships,
// every method that creates furniture must be updated.
const chair = style === 'modern' ? new ModernChair() : new VictorianChair();
const sofa = style === 'modern' ? new ModernSofa() : new VictorianSofa();
// Easy to mix accidentally:
const table = new ModernTable(); // oops — should have been Victorian
return { chair, sofa, table };
}
}Correct (one factory per family, compatibility guaranteed):
/**
* The Abstract Factory interface declares a set of methods that return
* different abstract products. These products are called a family and are
* related by a high-level theme or concept. Products of one family are usually
* able to collaborate among themselves. A family of products may have several
* variants, but the products of one variant are incompatible with products of
* another.
*/
interface AbstractFactory {
createProductA(): AbstractProductA;
createProductB(): AbstractProductB;
}
/**
* Concrete Factories produce a family of products that belong to a single
* variant. The factory guarantees that resulting products are compatible.
*/
class ConcreteFactory1 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA1();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA2();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB2();
}
}
interface AbstractProductA {
usefulFunctionA(): string;
}
class ConcreteProductA1 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A1.';
}
}
class ConcreteProductA2 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A2.';
}
}
interface AbstractProductB {
usefulFunctionB(): string;
anotherUsefulFunctionB(collaborator: AbstractProductA): string;
}
class ConcreteProductB1 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B1.';
}
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = collaborator.usefulFunctionA();
return `The result of the B1 collaborating with the (${result})`;
}
}
class ConcreteProductB2 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B2.';
}
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = collaborator.usefulFunctionA();
return `The result of the B2 collaborating with the (${result})`;
}
}
/**
* The client code works with factories and products only through abstract
* types: AbstractFactory and AbstractProduct. This lets you pass any factory or
* product subclass to the client code without breaking it.
*/
function clientCode(factory: AbstractFactory) {
const productA = factory.createProductA();
const productB = factory.createProductB();
console.log(productB.usefulFunctionB());
console.log(productB.anotherUsefulFunctionB(productA));
}
console.log('Client: Testing client code with the first factory type...');
clientCode(new ConcreteFactory1());
console.log('');
console.log('Client: Testing the same client code with the second factory type...');
clientCode(new ConcreteFactory2());Output:
Client: Testing client code with the first factory type...
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)
Client: Testing the same client code with the second factory type...
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)When to use
- Your code must work with several families of related products and you don't want it depending on concrete classes
- Product variants are unknown beforehand, or you want to add new ones without breaking client code
- A class containing several Factory Methods that all switch on the same variant — extract them into a family
When NOT to use
- You have only one product type — use Factory Method
- You have only one variant — there's no family to enforce
- Products in the family don't actually need to match — coupling them with one factory creates artificial coordination
Implementation Steps
1. Map distinct product types versus their variants (a matrix: product × variant) 2. Declare abstract product interfaces; implement concrete products per interface 3. Declare the abstract factory interface with one creation method per product type 4. Implement concrete factories for each variant 5. Add factory-initialization code that picks the right factory based on configuration or environment 6. Replace direct constructor calls with factory creation methods
Pros
- Guarantees product compatibility across families
- Avoids tight coupling between concrete products and client code
- Centralizes product creation (Single Responsibility)
- Enables new variants without breaking existing code (Open/Closed)
Cons
- Introduces significant complexity through new interfaces and classes — overkill for a single product type or single variant
Related Patterns
- Factory Method — Abstract Factory often evolves from a class with several Factory Methods
- Builder — Builder constructs one complex product step by step; Abstract Factory returns a family immediately
- Prototype — concrete factories may implement their methods via clone
- Facade — Abstract Factory can hide creation behind a simple Facade
- Singleton — concrete factories are often Singletons
Reference: refactoring.guru/design-patterns/abstract-factory
Use Builder to Construct Complex Objects Step by Step
Pattern intent: separate the construction of a complex object from its representation, so the same construction process can create different representations. Each construction step lives on a builder; an optional director orchestrates the sequence.
Shapes to recognize
- A constructor with 8+ parameters, many optional, often
null/undefinedplaceholders at call sites - Multiple overloaded constructors covering subsets of parameters (telescoping constructor)
- A subclass per parameter combination —
HouseWithGarageAndPool,HouseWithGarageOnly, etc. - Deferred construction — you want to assemble a tree of objects in stages without exposing the half-built result
- The "fluent builder" call chain:
new Q().select(...).where(...).limit(...).build()
Problem
Complex objects need many fields and nested objects initialized. Unwieldy constructors with numerous parameters bury intent at call sites; creating subclasses for every configuration explodes the class count.
Solution
Extract construction into a separate builder object that exposes one method per construction step. Clients call only the steps they need. Different concrete builders can produce different product representations from the same sequence of calls. An optional Director encapsulates well-known construction recipes.
Incorrect (telescoping constructor):
class House {
// Twelve parameters, every caller passes `null` for the ones they don't need.
constructor(
walls: number, roof: string, doors: number, windows: number,
garage: boolean, pool: boolean, garden: boolean, statues: number,
fence: boolean, solarPanels: boolean, ev: boolean, smart: boolean,
) { /* ... */ }
}
// Call site: which `false` corresponds to which feature?
const h = new House(4, 'tile', 2, 6, true, false, true, 0, false, true, false, true);Correct (steps on a builder; director orchestrates recipes):
/**
* The Builder interface specifies methods for creating the different parts of
* the Product objects.
*/
interface Builder {
producePartA(): void;
producePartB(): void;
producePartC(): void;
}
/**
* The Concrete Builder classes follow the Builder interface and provide
* specific implementations of the building steps. Your program may have several
* variations of Builders, implemented differently.
*/
class ConcreteBuilder1 implements Builder {
private product!: Product1;
constructor() {
this.reset();
}
public reset(): void {
this.product = new Product1();
}
public producePartA(): void {
this.product.parts.push('PartA1');
}
public producePartB(): void {
this.product.parts.push('PartB1');
}
public producePartC(): void {
this.product.parts.push('PartC1');
}
/**
* Concrete Builders are supposed to provide their own methods for
* retrieving results. Various types of builders may create entirely
* different products, so methods cannot be declared in the base Builder
* interface (at least in a statically typed language).
*/
public getProduct(): Product1 {
const result = this.product;
this.reset();
return result;
}
}
class Product1 {
public parts: string[] = [];
public listParts(): void {
console.log(`Product parts: ${this.parts.join(', ')}\n`);
}
}
/**
* The Director is only responsible for executing the building steps in a
* particular sequence. It is helpful when producing products according to a
* specific order or configuration. The Director is optional — clients can drive
* builders directly.
*/
class Director {
private builder!: Builder;
public setBuilder(builder: Builder): void {
this.builder = builder;
}
public buildMinimalViableProduct(): void {
this.builder.producePartA();
}
public buildFullFeaturedProduct(): void {
this.builder.producePartA();
this.builder.producePartB();
this.builder.producePartC();
}
}
function clientCode(director: Director) {
const builder = new ConcreteBuilder1();
director.setBuilder(builder);
console.log('Standard basic product:');
director.buildMinimalViableProduct();
builder.getProduct().listParts();
console.log('Standard full featured product:');
director.buildFullFeaturedProduct();
builder.getProduct().listParts();
// The Builder pattern can be used without a Director class.
console.log('Custom product:');
builder.producePartA();
builder.producePartC();
builder.getProduct().listParts();
}
const director = new Director();
clientCode(director);Output:
Standard basic product:
Product parts: PartA1
Standard full featured product:
Product parts: PartA1, PartB1, PartC1
Custom product:
Product parts: PartA1, PartC1When to use
- Eliminate the telescoping-constructor anti-pattern
- Create different representations of products using similar construction steps
- Construct Composite trees or other complex objects in stages
- Prevent client code from accessing the product before it is fully assembled
When NOT to use
- The object has 2-3 parameters with no optional combinations — a plain constructor or factory function suffices
- The product has only one representation and one construction path — Builder is overhead
- TypeScript's named-arguments via object literal already solves the readability problem and you don't need staged construction
Implementation Steps
1. Identify the discrete construction steps shared by all product representations 2. Declare these steps in a base Builder interface 3. Create a concrete builder per representation, each implementing all steps 4. Add a product-retrieval method on concrete builders (return type may vary by builder) 5. Optionally create a Director to encapsulate construction recipes 6. Client creates a builder (and optionally a director), runs the steps, and retrieves the product
Pros
- Construct objects incrementally; defer or recurse steps
- Reuse construction code across product variations
- Isolate complex construction logic (Single Responsibility)
Cons
- Overall code complexity increases due to multiple new classes
Related Patterns
- Factory Method — Builder often evolves from a class with many overloaded constructors
- Abstract Factory — Abstract Factory returns a family immediately; Builder builds one product step by step
- Composite — Builder is a natural fit for assembling Composite trees
- Bridge — director acts as the abstraction; builders as the implementations
- Singleton — concrete builders are often Singletons
Reference: refactoring.guru/design-patterns/builder
Use Factory Method to Decouple Object Creation from Concrete Classes
Pattern intent: an interface for creating objects in a superclass, but subclasses decide which concrete class to instantiate. The superclass holds the business logic that consumes products; subclasses pick the product type.
Shapes to recognize
- A class scattered with
new Truck(),new Ship(),new Drone()calls whose behavior is otherwise identical — adding a 4th transport requires hunting through the file - A
switch (type)block inside a constructor or static helper that returns different subclasses - A library you want users to extend, but the library hard-codes the products it creates
- "I want to override what gets instantiated, but I don't want to override the whole method"
Problem
A logistics app coupled to Truck faces difficulty when adding Ship: code that operates on transports is tightly coupled to the concrete class. Each new transport type spreads conditional logic across the codebase.
Solution
Replace direct construction calls with invocations of a factory method declared on the creator. Objects are still created with new, but the call lives inside the factory method, which subclasses override to return different products. All products share a common interface so callers stay product-agnostic.
Incorrect (caller couples directly to concrete classes):
class LogisticsApp {
planRoute(transportKind: 'truck' | 'ship') {
if (transportKind === 'truck') {
const truck = new Truck();
truck.deliver();
} else if (transportKind === 'ship') {
const ship = new Ship();
ship.deliver();
}
// Adding `new Drone()` here forces edits in every method that plans routes.
}
}Correct (subclasses override the factory method):
/**
* The Creator class declares the factory method that is supposed to return an
* object of a Product class. The Creator's subclasses usually provide the
* implementation of this method.
*/
abstract class Creator {
/**
* Note that the Creator may also provide some default implementation of the
* factory method.
*/
public abstract factoryMethod(): Product;
/**
* Also note that, despite its name, the Creator's primary responsibility is
* not creating products. Usually, it contains some core business logic that
* relies on Product objects, returned by the factory method. Subclasses can
* indirectly change that business logic by overriding the factory method
* and returning a different type of product from it.
*/
public someOperation(): string {
const product = this.factoryMethod();
return `Creator: The same creator's code has just worked with ${product.operation()}`;
}
}
/**
* Concrete Creators override the factory method in order to change the
* resulting product's type.
*/
class ConcreteCreator1 extends Creator {
public factoryMethod(): Product {
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator {
public factoryMethod(): Product {
return new ConcreteProduct2();
}
}
/**
* The Product interface declares the operations that all concrete products must
* implement.
*/
interface Product {
operation(): string;
}
class ConcreteProduct1 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct1}';
}
}
class ConcreteProduct2 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct2}';
}
}
/**
* The client code works with an instance of a concrete creator, albeit through
* its base interface. As long as the client keeps working with the creator via
* the base interface, you can pass it any creator's subclass.
*/
function clientCode(creator: Creator) {
console.log('Client: I\'m not aware of the creator\'s class, but it still works.');
console.log(creator.someOperation());
}
console.log('App: Launched with the ConcreteCreator1.');
clientCode(new ConcreteCreator1());
console.log('');
console.log('App: Launched with the ConcreteCreator2.');
clientCode(new ConcreteCreator2());Output:
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}When to use
- The exact type and dependencies of the objects your code instantiates are unknown beforehand
- You are building a library/framework and want users to extend internal components through inheritance
- You want to reuse existing objects (object pool, cache) instead of rebuilding them — the factory method is the natural place to insert the lookup
When NOT to use
- The number of product types is fixed and small, and the construction logic is trivial — a plain
newsuffices - You only need one variant — introducing a creator hierarchy is dead weight
- You need to vary a family of related objects together — reach for Abstract Factory instead
Implementation Steps
1. Ensure all products implement the same interface 2. Add an empty factory method to the creator class with return type matching the product interface 3. Replace product constructor references in the creator with factory-method calls 4. Create a creator subclass for each product type, overriding the factory method 5. If the creator has many product variants, consider passing control parameters to the factory method 6. Make the base factory method abstract, or provide a default implementation if there is a sensible default
Pros
- Avoids tight coupling between creator and concrete products
- Centralizes product creation, improving maintainability (Single Responsibility)
- Enables introducing new product types without breaking existing client code (Open/Closed)
Cons
- Code complexity increases due to numerous new subclasses required for implementation
Related Patterns
- Abstract Factory — often evolves from Factory Method when you need families of related products
- Prototype — alternative when inheritance is not desirable; clone configured instances instead
- Template Method — Factory Method is often a single step inside a Template Method
- Iterator — collections frequently expose iterators via a factory method
Use Prototype to Clone Objects Without Coupling to Concrete Classes
Pattern intent: copy existing objects without making code depend on their concrete classes. Each class implements a clone() method that produces an equivalent instance, including private fields the caller could never reach.
Shapes to recognize
- Manual "copy constructor" code that reads every field of an object — and breaks when private fields exist
- A registry of pre-configured "template" objects that callers want to duplicate, not subclass
- Need to copy an object received through an interface — you don't know the concrete class
- Avoiding deep recursive constructor chains by cloning a configured instance
Problem
Copying an object from outside requires reading its fields, which may be private or unknown. Code that depends on the object's concrete class to copy it becomes tightly coupled and breaks when only the interface is known.
Solution
Give each cloneable class a clone() method that returns an independent copy. The object copies its own state, so private fields are preserved. Optionally maintain a registry of frequently-used prototypes that clients can clone by name instead of constructing from scratch.
Incorrect (external copy code can't reach private fields):
class UserProfile {
public name: string;
private internalToken: string; // external copy code cannot see this
constructor(name: string, internalToken: string) {
this.name = name;
this.internalToken = internalToken;
}
}
function copyProfile(profile: UserProfile): UserProfile {
// We can only copy what's public — the internal token is silently lost.
return new UserProfile(profile.name, '');
}Correct (object clones itself):
/**
* The example class that has cloning ability. We'll see how the values of field
* with different types will be cloned.
*/
class Prototype {
public primitive: any;
public component!: object;
public circularReference!: ComponentWithBackReference;
public clone(): this {
const clone = Object.create(this);
clone.component = Object.create(this.component);
// Cloning an object that has a nested object with backreference
// requires special treatment. After the cloning is completed, the
// nested object should point to the cloned object, instead of the
// original object. Spread operator can be handy for this case.
clone.circularReference = new ComponentWithBackReference(clone);
return clone;
}
}
class ComponentWithBackReference {
public prototype;
constructor(prototype: Prototype) {
this.prototype = prototype;
}
}
function clientCode() {
const p1 = new Prototype();
p1.primitive = 245;
p1.component = new Date();
p1.circularReference = new ComponentWithBackReference(p1);
const p2 = p1.clone();
if (p1.primitive === p2.primitive) {
console.log('Primitive field values have been carried over to a clone. Yay!');
} else {
console.log('Primitive field values have not been copied. Booo!');
}
if (p1.component === p2.component) {
console.log('Simple component has not been cloned. Booo!');
} else {
console.log('Simple component has been cloned. Yay!');
}
if (p1.circularReference === p2.circularReference) {
console.log('Component with back reference has not been cloned. Booo!');
} else {
console.log('Component with back reference has been cloned. Yay!');
}
if (p1.circularReference.prototype === p2.circularReference.prototype) {
console.log('Component with back reference is linked to original object. Booo!');
} else {
console.log('Component with back reference is linked to the clone. Yay!');
}
}
clientCode();Output:
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!When to use
- Your code shouldn't depend on the concrete classes of objects you copy
- You receive objects through an interface and need to duplicate them
- You have many subclasses that differ only in initialization — replace them with pre-configured prototypes
- A central registry of configured prototypes can replace subclassing for "preset" variants
When NOT to use
- The object is a value (primitive, plain data record) — direct copy via spread or
structuredCloneis simpler - The object holds external resources (sockets, file handles, transactions) — cloning leaks them
- Circular references make safe deep-copy difficult and you don't need to clone — refactor the graph instead
Implementation Steps
1. Declare a prototype interface with a clone() method 2. Define an alternative constructor accepting an instance of the same class, copying every field 3. Override clone() explicitly in each class so it always returns the right concrete type 4. Optionally create a centralized prototype registry for frequently-used presets 5. Replace direct constructor calls with registry lookups when appropriate
Pros
- Clone objects without coupling to concrete classes
- Eliminate repeated initialization code by favoring pre-built prototypes
- Produce complex objects more conveniently than via inheritance
- Alternative to inheritance for configuration presets
Cons
- Cloning complex objects with circular references requires careful handling
- Easy to mistake a shallow copy for a deep copy
Related Patterns
- Factory Method — Prototype is an alternative when inheritance is undesirable
- Abstract Factory — concrete factories may store and clone prototypes instead of using
new - Memento — Prototype can be a simpler alternative for straightforward objects with mostly public state
- Composite/Decorator — benefit from Prototype when cloning structures instead of reconstructing
- Command — saving copies of commands for history uses Prototype
Reference: refactoring.guru/design-patterns/prototype
Related skills
FAQ
What does implementation-design-patterns do?
implementation-design-patterns is a Claude Code skill in the AI & Agent Building category.
When should I use implementation-design-patterns?
When you need to helps with ai & agent building tasks during ai-assisted development, or when implementation-design-patterns is a claude code skill in the ai & agent building category.
What are the main capabilities?
implementation-design-patterns; AI & Agent Building; AI-coding skill.