tool / 52
Design Patterns
The classic Gang of Four patterns, simplified — what they are, when to use them, what they look like.
All local
12/12
Creational4
Singleton
Ensure a class has only one instance.
When: When exactly one shared resource is needed (logger, config, connection pool).
class Logger {
static instance: Logger;
static getInstance() {
return this.instance ??= new Logger();
}
}Factory
Defer instantiation to a method.
When: When the exact type to create depends on input or context.
function shape(type) {
if (type === "circle") return new Circle();
if (type === "square") return new Square();
}Builder
Construct complex objects step by step.
When: When constructors would have too many parameters.
new QueryBuilder()
.from("users")
.where("active", true)
.orderBy("name")
.build();Prototype
Create new objects by cloning an existing one.
When: When object creation is expensive and you need many similar instances.
const base = { color: "red", size: 10 };
const copy = structuredClone(base);Structural4
Adapter
Make incompatible interfaces work together.
When: When integrating a third-party library that doesn't match your contract.
class StripeAdapter {
constructor(private stripe: Stripe) {}
charge(amount) { return this.stripe.paymentIntents.create({ amount }); }
}Decorator
Add behavior without modifying the original.
When: When you need to extend behavior dynamically and selectively.
function withLogging(fn) {
return (...args) => {
console.log("calling", fn.name, args);
return fn(...args);
};
}Facade
Provide a simple interface to a complex subsystem.
When: When you want to hide internal complexity from consumers.
class VideoConverter {
convert(file, format) {
const decoded = decoder.decode(file);
const reencoded = encoder.encode(decoded, format);
return container.wrap(reencoded);
}
}Proxy
Provide a surrogate for another object.
When: For lazy loading, access control, caching, or logging.
const apiProxy = new Proxy(api, {
get(target, prop) {
cache[prop] ??= target[prop];
return cache[prop];
},
});Behavioral4
Observer
Notify many objects when one changes.
When: Event systems, reactive UI, pub/sub.
class Emitter {
listeners = new Set();
on(fn) { this.listeners.add(fn); }
emit(data) { this.listeners.forEach(fn => fn(data)); }
}Strategy
Encapsulate interchangeable algorithms.
When: When you have multiple ways to do the same thing and want to swap them.
const sorts = {
asc: (a, b) => a - b,
desc: (a, b) => b - a,
};
arr.sort(sorts[direction]);Command
Wrap a request as an object.
When: When you need undo/redo, queuing or logging of operations.
class AddCommand {
constructor(private list, private item) {}
execute() { this.list.push(this.item); }
undo() { this.list.pop(); }
}State
Change behavior when state changes.
When: When an object's behavior depends on its state and there are many states.
class Door {
state = "closed";
open() { if (this.state === "closed") this.state = "open"; }
close() { if (this.state === "open") this.state = "closed"; }
}