A collection of 94 software design, architecture, concurrency, functional, and distributed systems patterns implemented in TypeScript.
The goal of this repository is to provide simple and (hopefully) readable examples of different design patterns and their implementations. Each is implemented as a standalone TypeScript file with no dependencies (except for concurrency patterns, which use node:worker_threads to achieve parallelism and node:path for spawning the worker). The worker files in the concurrency folder are not runnable directly, they have to be started by their respective main file (i.e. they have to be a worker).
This repository is meant for educational purposes and the provided examples are NOT production-ready code. In some of the files I included information on what a production-ready implementation would include, but it does not mean that patterns that don't have these disclaimers can be copied and pasted into your production codebases. They are intended to be read and understood so you can apply them intentionally in your next project. All examples simplify the infrastructure as much as possible, focusing only on the actual pattern.
All of the files contain lots of comments which can hopefully help you understand why these patterns are designed that way. If you want to let the code speak for itself without me talking over it, you can view the no-comments branch.
I am always open to all forms of feedback! If you spot a mistake or a bug in any of the examples, or you simply want to contribute, feel free to open issues or pull requests.
Since every file is self-contained, you can simply run it directly using tools like ts-node:
npx ts-node behavioral/visitor.ts
or compile the project and run the generated JavaScript files:
tsc
node dist/concurrency/mutex.js
Note: The RAII example requires explicit resource management, which is currently a Stage 3 proposal. Make sure that your Node version supports it and you're on TypeScript 5.2+.
Here is a list of all covered patterns. It also has more formal descriptions of each of the patterns, similar to what you may find in literature (some are shamelessly ripped from the Wikipedia article about Software design patterns).
| Name | Description | File |
|---|---|---|
| Abstract Factory | Creates families of related objects without specifying concrete classes | abstractFactory.ts |
| Builder | Separates the construction of objects from their representation | builder.ts |
| Dependency Injection | Provides dependencies from outside instead of creating them internally | dependencyInjection.ts |
| Dependency Injection Container | Manages object creation and automatically resolves dependencies using a centralized container | dependencyInjectionContainer.ts |
| Faceted Builder | Splits a builder into multiple smaller builders | facetedBuilder.ts |
| Factory | Encapsulates object creation in a function or a class | factory.ts |
| Factory Method | Defines an interface for creating objects while allowing subclasses to decide the type | factoryMethod.ts |
| Fluent Builder | Connects the Builder and Fluent Interface patterns | fluentBuilder.ts |
| Lazy Initialization | Delays object creation until it's actually needed | lazyInitialization.ts |
| Multiton | Maintains multiple selectable instances of a class | multiton.ts |
| Object Pool | Reuses expensive objects instead of recreating them | objectPool.ts |
| Parameter Object | Bundles parameters into a single object to simplify method signatures | parameterObject.ts |
| Prototype | Creates objects by cloning existing instances | prototype.ts |
| RAII (Resource Acquisition is Initialization) | Ties resource management to object lifecycle. Since JavaScript uses a Garbage Collector, this is not true RAII and instead uses the using keyword |
raii.ts |
| Service Locator | Provides a central registry for retrieving services | serviceLocator.ts |
| Singleton | Ensures a class has only one instance | singleton.ts |
| Name | Description | File |
|---|---|---|
| Adapter | Allows incompatible interfaces to work together | adapter.ts |
| Authority Inversion | Moves authority over children from parent to a shared authority | authorityInversion.ts |
| Bridge | Separates abstraction from implementation | bridge.ts |
| Composite | Treats individual objects and compositions of objects uniformly | composite.ts |
| Decorator | Dynamically adds behavior to objects | decorator.ts |
| Delegation | Passes responsibility to a different object | delegation.ts |
| Effectivity | Stores effectivity ranges to allow client to check whether object was in effect at a given date | effectivity.ts |
| Extension Object | Adds new functionality without modifying existing objects | extensionObject.ts |
| Facade | Provides a simplified interface to a complex system | facade.ts |
| Flyweight | Reduces memory usage by sharing common state | flyweight.ts |
| Front Controller | Centralizes request handling | frontController.ts |
| Marker | Uses empty interfaces (or, in this case, classes) to attach metadata | marker.ts |
| Mixin | Adds reusable behavior to a class through functions that extend a base class, allowing a form of multiple inheritance. Credit: MusicMakerOwO | mixin.ts |
| Module | Groups several related elements into a single entity. This specific implementation is a revealing module, which exposes some of these elements publicly | module.ts |
| Negative Cache (Negative Space) | Caches non-existent objects to avoid expensive queries | negativeCache.ts |
| Proxy | Controls access to another object | proxy.ts |
| Registry | Provides centralized object lookup | registry.ts |
| Repository | Mediates between domain and data layers by exposing a collection-like interface | repository.ts |
| Temporal Collection | Stores temporal objects in a continuous timeline collection, keyed by milestones | temporalCollection.ts |
| Twin | Uses paired objects to extend functionality. This pattern also allows multiple inheritance in languages that do not support it | twin.ts |
| Name | Description | File |
|---|---|---|
| Audit Log | Tracks read-only time-stamped actions for moderative purposes | auditLog.ts |
| Blackboard | Allows multiple agents to collaborate through shared knowledge | blackboard.ts |
| Chain-of-responsibility | Passes requests through a chain of handlers | chainOfResponsibility.ts |
| Circuit Breaker | Temporarily blocks requests to failing services to prevent cascading failures | circuitBreaker.ts |
| Command | Encapsulates requests as objects | command.ts |
| Fluent Interface | Provides method chaining APIs | fluentInterface.ts |
| Intent Queue | Centralizes action processing via unified intents | intentQueue.ts |
| Interpreter | Defines a representation for language grammar. In this case implemented as an expression tree, skipping tokenizing and parsing | interpreter.ts |
| Iterator | Provides a way to access elements of an aggregate without exposing the internal representation. In this case implemented using JavaScript iterators | iterator.ts |
| Mediator | Centralizes communication between objects | mediator.ts |
| Memento | Captures and restores object state | memento.ts |
| Middleware | Passes requests through a chain of composable handlers | middleware.ts |
| Null Object | Provides a default object instead of relying on null checks |
nullObject.ts |
| Observer | Defines a one-to-many dependency where objects are notified of state changes | observer.ts |
| Pipeline | Passes values through multiple stages, each transforming the output of the previous one | pipeline.ts |
| Retry | Automatically re-attempts failed requests based on a specified policy | retry.ts |
| Scheduler | Controls the execution of tasks over time | scheduler.ts |
| Servant | Provides a common functionality for a group of objects | servant.ts |
| Specification | Encapsulates business rules as reusable specifications with Boolean algebra | specification.ts |
| State | Changes behavior when internal state changes | state.ts |
| Strategy | Defines interchangeable algorithms | strategy.ts |
| Template Method | Defines an algorithm skeleton while allowing steps to vary | templateMethod.ts |
| Unit of Work | Groups operations into a single unit that can be rolled back and committed | unitOfWork.ts |
| Visitor | Separates operations from object structures | visitor.ts |
| Name | Description | File |
|---|---|---|
| Composition | Combines multiple functions into a single function | composition.ts |
| Currying | Transforms a function with multiple parameters into a sequence of single-parameter functions | currying.ts |
| Either | Represents a value that is one of two types | either.ts |
| Lens | Allows getting and setting values within immutable nested data structures in a composable way | lens.ts |
| Memoization | Caches results of expensive functions to prevent redundant recalculation | memoization.ts |
| Option | Represents a value that may or may not be present | option.ts |
| Partial Apply (papply) | Applies some arguments ahead of time, producing a function with fewer parameters | partialApply.ts |
| Result | Represents a value as either a success or a failure; a special case of Either | result.ts |
| State monad | Passes state through a series of computations in a purely functional way. Not to be confused with the behavioral State: this is an implementation of the State monad, whereas the behavioral State refers to a system where the behavior is changed along with the state | state.ts |
| Name | Description | File |
|---|---|---|
| Choreography Saga | Coordinates a transaction through events passed between services with no central coordinator. Choreography and Orchestration Sagas are often regarded as the same pattern, just two different implementations, but I chose to include them as separate patterns to show the differences between them | choreographySaga.ts |
| CQRS (Command-Query Responsibility Segregation) | Separates read and write into separate models | cqrs.ts |
| Dead Letter Queue | Stores undeliverable messages so they can be inspected later | deadLetterQueue.ts |
| Event Bus (Pub/Sub) | Lets decoupled services publish and subscribe to events | eventBus.ts |
| Event Sourcing | Stores state changes as a sequence of events | eventSourcing.ts |
| Event Store | Provides bitemporal storage for events | eventStore.ts |
| Inbox | Stores processed messages to achieve idempotency | inbox.ts |
| Message Translator | Translates incompatible message contracts | messageTranslator.ts |
| Orchestration Saga | Coordinates a transaction through events and commands dispatched by a central coordinator | orchestrationSaga.ts |
| Outbox | Ensures reliable delivery by storing messages in the same transaction as the data change | outbox.ts |
| Name | Description | File |
|---|---|---|
| Active Object | Decouples method execution from method invocation | activeObject.ts |
| Actor Model | Encapsulates state and behavior in isolated actors communicating only through messages | actorModel.ts |
| Balking | Ignores actions if the object is in a state that does not allow it to perform them | balking.ts |
| Guarded Suspension | Delays actions until the object is able to perform them | guardedSuspension.ts |
| Mutex | Ensures only one thread can access a shared resource at a time. This implementation does not guarantee fairness | mutex.ts |
| Read-Write Lock (RWLock) | Allows concurrent reads but requires exclusive access for writes. This implementation does not guarantee fairness | readWriteLock.ts |
| Semaphore | Limits the number of concurrent accesses to a resource. This implementation does not guarantee fairness | semaphore.ts |
| Thread pool | Reuses a fixed set of threads to perform tasks | threadPool.ts |
| Name | Description | File |
|---|---|---|
| Fake | Provides a working but simplified implementation for testing | fake.ts |
| Mock | Verifies the expected interactions occurred during a test | mock.ts |
| Object Mother | Provides reusable test object creation methods | objectMother.ts |
| Spy | Records information about calls for later verification | spy.ts |
| Stub | Returns predefined responses to calls | stub.ts |
| Test Data Builder | Constructs complex data structures incrementally | testDataBuilder.ts |
| Test Object | Supplies a preconfigured object for reuse across tests | testObject.ts |