| name | software-best-design-patterns |
| description | 2026-edition design-patterns reference for TypeScript 6. Covers all 23 Gang-of-Four patterns, PoEAA enterprise patterns (Repository, Unit of Work, Service Layer, DTO, Identity Map, Lazy Load), modern TS idioms (Result, Discriminated Union State, Branded Types, Type-State, Dependency Injection, Hooks, Signals, Reducer, Middleware), resilience (Circuit Breaker, Retry+Backoff, Saga, Outbox, Idempotency), plus references on SOLID, anti-patterns, TS 6 features (strict-default, ES2025, Temporal, satisfies, const params, NoInfer, using), functional patterns, architectural styles (Hexagonal, Clean, CQRS, Event Sourcing, DDD), and testing. Use when designing architecture, refactoring code, or when code exhibits symptoms like growing switch statements, deep inheritance, tight coupling, dual-write inconsistencies, throwing for expected errors, stringly-typed IDs, unbounded retry loops, or boolean state flags. |
Software Best Design Patterns
A 2026-edition update of the Gang-of-Four catalog, written for TypeScript 6 (Mar 2026 — strict by default, ESM by default, es2025 target, Temporal types, ES2025 iterator and set helpers). Covers all 23 classical patterns, the enterprise patterns from Patterns of Enterprise Application Architecture, the modern functional/reactive patterns that have become idiomatic (Result, Discriminated Union State, Branded Types, DI, Hooks, Signals, Reducer, Middleware, Type-State), and the resilience patterns that distributed systems require (Circuit Breaker, Retry, Saga, Outbox, Idempotency).
Core philosophy. Patterns are templates you adapt to your context, not blueprints to copy. They earn their weight when they genuinely simplify your design. Reaching for one to "look senior" is the most reliable way to make code worse.
How to use this skill
- Skim the decision trees for your symptom or problem class.
- Read the matching pattern file in
patterns/ — each has Intent, Problem, Solution, Modern TypeScript Twist, When to Use, When NOT to Use, and Testing.
- Consult a
references/ doc for cross-cutting concerns (principles, anti-patterns, modern TS, functional patterns, architectural styles, testing).
- Apply the Implementation checklist before merging.
Skip ahead if you already know the pattern's name — Read the file directly.
Quick lookup (CLI)
scripts/find-pattern.ts gives deterministic name/symptom/category lookups without scanning all 42 pattern files. Runs on Node 22.7+ (with --experimental-strip-types), Node 23.6+ stable, Bun, or Deno.
./scripts/find-pattern.ts list
./scripts/find-pattern.ts search "growing switch"
./scripts/find-pattern.ts show strategy
./scripts/find-pattern.ts category modern
./scripts/find-pattern.ts symptoms
node --experimental-strip-types scripts/find-pattern.ts list
bun scripts/find-pattern.ts list
deno run --allow-read scripts/find-pattern.ts list
Use it to discover the right pattern file when a symptom rings a bell but you don't remember the name. Then Read the path it prints.
Foundational principles
Every pattern in this skill is an expression of one of these. Master the principles and patterns follow naturally. Full discussion: references/principles.md.
| Principle | Meaning | Symptom of violation |
|---|
| Encapsulate what varies | Isolate changing parts from stable parts | Changes ripple through codebase |
| Program to interfaces | Depend on abstractions, not concretions | Can't swap implementations |
| Composition over inheritance | Build behaviour by composing objects | Deep, rigid class hierarchies |
| Single Responsibility (SRP) | One reason to change per module | Classes that do many things |
| Open–Closed (OCP) | Open for extension, closed for modification | Adding a feature edits old code |
| Liskov Substitution (LSP) | Subtypes must be substitutable | Subclasses throw "not supported" |
| Interface Segregation (ISP) | Don't depend on methods you don't use | Mocks with 20 stub methods |
| Dependency Inversion (DIP) | High-level depends on abstractions | Domain imports vendor SDK |
| Tell, don't ask | Tell objects what to do, don't fetch their state | Callers read state to make decisions |
| Law of Demeter | Talk only to immediate friends | a.b.c.d.e chains |
| YAGNI / KISS / DRY | Don't add until needed; keep it simple; don't duplicate knowledge | Premature abstraction; wrong abstraction |
| Functional core, imperative shell | Pure logic inside, I/O at the edges | Logic woven with I/O is hard to test |
How to choose a pattern
By problem (what are you trying to do?)
CREATING OBJECTS
├── Multi-step / fluent construction ──────────► Builder
├── Complex / conditional creation ────────────► Factory Method
├── Family of related objects together ────────► Abstract Factory
├── Copy existing objects ─────────────────────► Prototype
├── Exactly one instance ──────────────────────► Singleton (PREFER DI)
└── Many shared lightweight instances ────────► Flyweight
STRUCTURING / ADAPTING OBJECTS
├── Make incompatible interfaces work ────────► Adapter
├── Simplify complex subsystem ───────────────► Facade
├── Tree / part-whole hierarchy ──────────────► Composite
├── Add behavior dynamically ─────────────────► Decorator
├── Control access / lazy / remote ──────────► Proxy
├── Two orthogonal axes of variation ────────► Bridge
└── Convert one interface across boundaries ─► Adapter
BEHAVIOR / ALGORITHMS
├── Swap algorithms at runtime ────────────────► Strategy
├── Behavior changes with state ──────────────► State (or Discriminated Union State)
├── Algorithm skeleton with hooks ───────────► Template Method
├── Encapsulate requests as objects ─────────► Command
├── Undo / redo / time-travel ───────────────► Memento + Command
├── Add ops to closed hierarchy ─────────────► Visitor (TS prefer: Discriminated Union + switch)
├── One-to-many notification ────────────────► Observer (or Signals/Hooks)
├── Sequential handlers / pipeline ──────────► Chain of Responsibility / Middleware
├── Reduce N-to-N coupling ──────────────────► Mediator
└── Sequential traversal of collection ──────► Iterator
DATA ACCESS / PERSISTENCE
├── Abstract data source ────────────────────► Repository
├── Atomic multi-entity commit ──────────────► Unit of Work + DB transaction
├── Object identity per session ─────────────► Identity Map
├── Defer expensive loading ─────────────────► Lazy Load
└── Shape data for transfer ─────────────────► DTO + Zod/Valibot at boundary
ERRORS / CONTROL FLOW (modern)
├── Expected failure modes typed ────────────► Result
├── Multiple valid states ───────────────────► Discriminated Union State
├── Compile-time misuse prevention ──────────► Type-State
└── Distinct primitive-shaped identities ────► Branded Types
UI / FRONTEND
├── Reusable stateful logic ─────────────────► Hooks
├── Fine-grained reactive state ─────────────► Signals
├── Predictable transitions ─────────────────► Reducer
└── Cross-cutting request handling ──────────► Middleware
WIRING / COMPOSITION
└── Inject collaborators from outside ───────► Dependency Injection
DISTRIBUTED / RESILIENCE
├── Failing dependency cascades ─────────────► Circuit Breaker
├── Transient failures ──────────────────────► Retry + Backoff
├── Safe to retry mutating writes ───────────► Idempotency Key
├── Multi-service transaction ───────────────► Saga
└── DB write + event publish atomically ────► Outbox
By symptom (what does the code look like?)
| Symptom | Likely fix |
|---|
Growing switch / if-else chain on a type | Strategy, Discriminated Union State, Visitor |
| Duplicate code across siblings | Template Method, Strategy |
| One class does everything | SRP, Service Layer, Facade |
| Domain imports vendor SDK | DIP, Dependency Injection, Ports & Adapters |
| Constructor takes 8+ params | Object params, Builder |
| Adding feature edits 5 files | OCP, Strategy, plugin/registry |
| Adding features bloats class | Decorator, Strategy, Composition |
| Need undo/redo | Command + Memento |
| Object behaviour depends on state | State or Discriminated Union State |
Throwing Error for expected failures | Result |
string for IDs and emails | Branded Types + schema parsing |
loading + data + error booleans | Discriminated Union State |
| Stateful component logic duplicated | Hooks |
| Cross-cutting concern repeated | Middleware, Decorator |
| Retry loop blasts failing service | Circuit Breaker |
| Dual-write inconsistency (DB + broker) | Outbox |
| Cross-service workflow with rollback needs | Saga |
| Singleton hell | Dependency Injection |
Untyped any at boundary | Schema parse (Zod/Valibot/ArkType) + Branded Types |
For more symptoms, see references/anti-patterns.md.
Domain logic: Transaction Script vs. Domain Model
| Factor | Transaction Script | Domain Model |
|---|
| Logic complexity | Simple (< 500 lines) | Complex, many rules |
| Business rules | Few, straightforward | Many, interacting |
| Operations | CRUD-heavy | Rich behaviour |
| Team / timeline | Small team, quick delivery | Long-term maintenance |
| Testing | Integration tests | Unit tests on the domain |
Rule of thumb: Start with Transaction Script. Refactor to Domain Model when procedural code becomes hard to maintain. See references/architectural-styles.md.
Pattern catalog
Each pattern has its own file with detailed examples, modern TypeScript idioms, real-world applications, pitfalls, related patterns, and testing patterns.
Creational
| Pattern | One-liner | File |
|---|
| Factory (Method / Simple / Abstract) | Encapsulate object creation | factory.md |
| Abstract Factory | Family of related objects, swap as a unit | abstract-factory.md |
| Builder | Step-by-step construction; fluent APIs | builder.md |
| Prototype | Clone an existing instance instead of constructing | prototype.md |
| Singleton | One instance — prefer DI | singleton.md |
Structural
| Pattern | One-liner | File |
|---|
| Adapter | Convert one interface to another | adapter.md |
| Bridge | Decouple two orthogonal hierarchies via composition | bridge.md |
| Composite | Uniform tree of parts and wholes | composite.md |
| Decorator | Add behaviour dynamically without subclassing | decorator.md |
| Facade | One simple surface over a complex subsystem | facade.md |
| Flyweight | Share heavy intrinsic state across many instances | flyweight.md |
| Proxy | Stand-in that controls access to the real thing | proxy.md |
Behavioral
| Pattern | One-liner | File |
|---|
| Chain of Responsibility | Pass request through handlers until one acts | chain-of-responsibility.md |
| Command | Encapsulate requests as objects | command.md |
| Iterator | Sequential access without exposing structure | iterator.md |
| Mediator | Centralise complex N-to-N communication | mediator.md |
| Memento | Snapshot internal state for restoration | memento.md |
| Observer | Notify dependents of state changes | observer.md |
| State | Behaviour changes with internal state | state.md |
| Strategy | Interchangeable algorithms behind one interface | strategy.md |
| Template Method | Algorithm skeleton with overridable hooks | template-method.md |
| Visitor | Add operations to a closed hierarchy | visitor.md |
Enterprise / PoEAA
Modern (TypeScript-native)
These patterns aren't in the GoF book but are core idioms in 2026 TypeScript.
| Pattern | One-liner | File |
|---|
| Result / Either | Failure as a typed value, not an exception | result.md |
| Discriminated Union State | Tagged unions for state — make illegal states unrepresentable | discriminated-union-state.md |
| Branded Types | Nominal-style identity in a structural type system | branded-types.md |
| Type-State | Encode state machines in the type system | type-state.md |
| Dependency Injection | Modern replacement for Singleton & Service Locator | dependency-injection.md |
| Middleware | Onion-shaped composable request/response pipeline | middleware.md |
| Reducer | (state, action) → state; functional cousin of State | reducer.md |
| Hooks | Reusable stateful logic in components | hooks.md |
| Signals | Fine-grained reactive primitive | signals.md |
Resilience / distributed
| Pattern | One-liner | File |
|---|
| Circuit Breaker | Stop hammering failing dependencies | circuit-breaker.md |
| Retry + Backoff | Handle transient failures with bounded, jittered retries | retry-backoff.md |
| Saga | Coordinate cross-service workflows with compensation | saga.md |
| Outbox | Atomic DB-change-plus-event publication | outbox.md |
| Idempotency | Make retries safe at the API and consumer boundaries | idempotency.md |
References (load when needed)
Cross-cutting documents that several patterns share. Don't read them up-front; consult when the situation calls.
| Reference | When to read |
|---|
| references/principles.md | When a pattern doesn't fit and you need to reason from first principles — SOLID, composition, coupling, cohesion, YAGNI/KISS/DRY |
| references/anti-patterns.md | When you spot a smell (God Object, Singleton abuse, Dual Write, Stringly-typed API, etc.) and need the matching remedy |
| references/modern-typescript.md | When applying TS 6 features to a pattern — the new defaults (strict, esnext, es2025), satisfies, const type parameters, NoInfer, using, Stage-3 decorators, branded types, iterator helpers, set methods, Map upsert, Temporal |
| references/functional-patterns.md | When working with Result, ADTs, pipelines, immutability, lenses, or Railway-Oriented Programming |
| references/architectural-styles.md | When choosing between layered, hexagonal, clean, modular monolith, microservices, CQRS, event sourcing, DDD tactical |
| references/testing-patterns.md | When deciding on test doubles, fixtures, property-based tests, contract tests, or wiring Vitest / Playwright / MSW |
Modern variations (what GoF would write today)
| Modern pattern | Based on | What changed |
|---|
| Hooks (React, Vue, Solid, Svelte) | Observer + Strategy + Template Method | Composable stateful logic at function granularity |
| Signals (Solid, Vue, Svelte 5, Angular, Preact, TC39 proposal) | Observer | Automatic dependency tracking; surgical updates |
| Middleware | Decorator + Chain of Responsibility | Onion-shaped pipelines, native to every HTTP framework |
| Reducer / Redux | Command + State | Pure (state, action) → state, replayable history |
| Event Sourcing | Command + Memento | Persist events; state is a fold over the log |
| CQRS | Separation of concerns | Read model decoupled from write model |
| Dependency Injection | Strategy + Factory | Container creates and injects; constructor-injection is the default |
| Hexagonal / Clean | DIP applied at app scale | Domain at the centre, infrastructure as adapters |
| Result types | Tagged union | Errors are values, not exceptions |
| Discriminated Unions | State + Visitor | Type-checked state machines, exhaustive matching |
Common mistakes
| Mistake | Symptom | Fix |
|---|
| Pattern overuse | Simple things require navigating five classes | Use only when solving a real problem |
| Wrong pattern | Code feels forced or awkward | Re-examine the actual problem |
| Inheritance abuse | Deep hierarchies, fragile base class | Favor composition (Strategy, Decorator, Bridge) |
| Singleton abuse | Global state, hidden dependencies, hard to test | Dependency injection |
| Premature abstraction | Interfaces with one implementation | Wait for the second variant |
| Throwing for control flow | try/catch blocks scattered for expected outcomes | Result type |
| Stringly-typed APIs | IDs and emails as string; easy to mix up | Branded types + schema parsing at boundaries |
| Booleans for state | loading + error + data flags | Discriminated union state |
| Dual writes | DB save + broker publish without coordination | Outbox pattern |
| Unbounded retries | while (true) try { … } | Retry + Backoff with maxAttempts |
| Non-idempotent writes retried | Duplicate charges, duplicate emails | Idempotency keys |
| Cross-service transactions | Trying to wrap multiple services in one transaction | Saga |
| God Service Locator | Components call Container.get<T>() from inside | Constructor injection |
More: references/anti-patterns.md.
Pattern selection workflow
When you're about to add structure, run through this in order:
- Is there a real problem?
- Not "I anticipate needing this someday." YAGNI it.
- Yes → continue.
- Is the simplest design enough?
- A function? A struct? A flat object?
- If "no, because…", continue.
- Which principle is being violated by the current shape?
- SRP? OCP? DIP? Tell-don't-ask?
- The principle picks the pattern.
- What's the smallest pattern that resolves the violation?
- Strategy beats Visitor for "one varying algorithm".
- DI beats Singleton for "one shared collaborator".
- Does the team know it?
- If yes → apply.
- If no → consider a smaller step (extract function; introduce interface) before the full pattern.
- Are you sure?
- Try writing the test first. If the test is awkward, the pattern is wrong.
- Add it. Then delete what the new shape made redundant.
Implementation checklist
Before merging code that introduces a pattern:
TypeScript baseline (assumed throughout)
This skill assumes:
- TypeScript 6.0 (Mar 2026).
strict, module: "esnext", and target: "es2025" are the defaults — no need to set them.
- Additionally enable
noUncheckedIndexedAccess and exactOptionalPropertyTypes for the strict baseline that catches the most bugs.
- Native ESM (
"type": "module" in package.json).
- Node 22 LTS / Node 24+, Bun, Deno, or a modern evergreen browser runtime.
- A schema library (Zod 4 / Valibot / ArkType — anything implementing Standard Schema) at trust boundaries.
- Vitest for unit and integration tests.
Temporal over Date for date/time work (ships as types in TS 6's esnext lib).
If you're on an older TypeScript line or still using CommonJS, the patterns themselves are unchanged; you'll just have a noisier tsconfig.json and a few more explicit imports.
Reading order suggestions
For someone new to design patterns:
- references/principles.md — the bedrock.
- patterns/strategy.md, patterns/factory.md, patterns/observer.md, patterns/decorator.md — the four most-used GoF patterns.
- patterns/result.md, patterns/discriminated-union-state.md, patterns/branded-types.md, patterns/dependency-injection.md — the four most-used modern TS patterns.
- references/anti-patterns.md — what to look out for in existing code.
For someone working on a distributed system:
- references/architectural-styles.md — pick a style.
- patterns/outbox.md, patterns/idempotency.md, patterns/saga.md — the must-haves.
- patterns/circuit-breaker.md, patterns/retry-backoff.md — the safety net.
- patterns/result.md, patterns/middleware.md — the connective tissue.
Final word
Design patterns are the names we give to the shapes we keep recognising.
A pattern's value is the shared vocabulary it gives a team and the tested template it provides for a known-shape problem. Knowing the catalog means you can describe a refactor in a sentence and others understand exactly what shape you're heading toward.
Patterns don't replace judgement. They are crystallised judgement — the captured experience of thousands of programmers who hit the same problems and converged on the same solutions. Use them when the situation matches; ignore them when it doesn't.
When in doubt, write less code.