Creational, structural and behavioral patterns with practical coding use-cases and interview notes.
SOLID are five design principles introduced by Robert C. Martin that make software designs more understandable, flexible, and maintainable. They are the foundation of good object-oriented design and are frequently tested in technical interviews.
| Principle | Name | Core Idea | Key Benefit |
|---|---|---|---|
| S | Single Responsibility | A class should have only one reason to change | Reduced coupling, easier maintenance |
| O | Open / Closed | Open for extension, closed for modification | Add features without breaking existing code |
| L | Liskov Substitution | Subtypes must be substitutable for their base types | Reliable polymorphism, no surprises |
| I | Interface Segregation | No unused methods, cleaner contracts | |
| D | Dependency Inversion | Depend on abstractions, not concretions | Loose coupling, easier testing (DI) |
A class should have only one reason to change. Each responsibility is an axis of change; when responsibilities are mixed, changing one can break the other.
# ── BAD: God class with mixed responsibilities ──
class User:
def __init__(self, name, email):
self.name = name
self.email = email
def save_to_database(self): ... # persistence
def send_email(self, message): ... # notification
def generate_report(self): ... # reporting
# ── GOOD: Single responsibility per class ──
class User:
def __init__(self, name, email):
self.name = name
self.email = email
class UserRepository:
def save(self, user):
print(f"Saving {user.name} to database")
class EmailService:
def send(self, to, message):
print(f"Sending '{message}' to {to}")
class ReportGenerator:
def generate(self, user):
print(f"Generating report for {user.name}")
# Usage
user = User("Alice", "alice@example.com")
UserRepository().save(user)
EmailService().send(user.email, "Welcome!")
ReportGenerator().generate(user)Software entities should be open for extension but closed for modification. You add new behavior by adding new code, not by changing existing, tested code.
// ── BAD: Modifying existing class for every new shape ──
class AreaCalculator {
calculate(shape: any): number {
if (shape.type === "circle")
return Math.PI * shape.radius ** 2;
if (shape.type === "rectangle") // must modify for each new shape!
return shape.width * shape.height;
throw new Error("Unknown shape");
}
}
// ── GOOD: Extend via polymorphism ──
interface Shape {
area(): number;
}
class Circle implements Shape {
constructor(public radius: number) {}
area(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
area(): number {
return this.width * this.height;
}
}
// Adding a new shape NEVER modifies AreaCalculator
class Triangle implements Shape {
constructor(public base: number, public height: number) {}
area(): number {
return 0.5 * this.base * this.height;
}
}
function totalArea(shapes: Shape[]): number {
return shapes.reduce((sum, s) => sum + s.area(), 0);
}
console.log(totalArea([
new Circle(5), new Rectangle(4, 6), new Triangle(3, 4)
]));Objects of a superclass should be replaceable with objects of a subclass without breaking the program. Subclasses must honor the contract of their parent.
# ── BAD: Square violates Rectangle contract ──
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def set_width(self, w): self.width = w
def set_height(self, h): self.height = h
def area(self): return self.width * self.height
class Square(Rectangle):
def set_width(self, w): self.width = self.height = w # breaks Rectangle!
def set_height(self, h): self.width = self.height = h # breaks Rectangle!
# Client code breaks:
def print_area(rect):
rect.set_width(5)
rect.set_height(4)
print(rect.area()) # Rectangle=20, Square=16 (SURPRISE!)
# ── GOOD: Separate hierarchy with proper abstraction ──
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
# Both can be used wherever Shape is expected
for shape in [Rectangle(5, 4), Square(5)]:
print(shape.area()) # 20, 25 — correctClients should not be forced to depend on methods they do not use. Prefer many small, focused interfaces over one large, general-purpose interface.
// ── BAD: Fat interface forces unused methods ──
interface Worker {
work(): void;
eat(): void;
sleep(): void;
}
class Robot implements Worker {
work() { console.log("Working..."); }
eat() { /* Robot doesn't eat! Forced to implement */ }
sleep() { /* Robot doesn't sleep! Forced to implement */ }
}
// ── GOOD: Segregated, role-specific interfaces ──
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
class Human implements Workable, Eatable, Sleepable {
work() { console.log("Working..."); }
eat() { console.log("Eating lunch"); }
sleep() { console.log("Sleeping 8h"); }
}
class Robot implements Workable {
work() { console.log("Working 24/7..."); }
}
// Each client depends only on what it needs
function makeWork(w: Workable) {
w.work();
}High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions. This enables Dependency Injection.
// ── BAD: High-level class depends on concrete implementation ──
class MySQLDatabase {
save(data: string) { console.log("Saving to MySQL:", data); }
}
class UserService {
private db = new MySQLDatabase(); // tight coupling!
saveUser(name: string) {
this.db.save(name);
}
}
// ── GOOD: Depend on abstraction + dependency injection ──
interface Database {
save(data: string): void;
}
class MySQLDatabase implements Database {
save(data: string) { console.log("MySQL:", data); }
}
class PostgresDatabase implements Database {
save(data: string) { console.log("Postgres:", data); }
}
class MockDatabase implements Database {
save(data: string) { console.log("Mock:", data); }
}
class UserService {
// Inject dependency via constructor
constructor(private db: Database) {}
saveUser(name: string) {
this.db.save(name);
}
}
// Swap implementations without changing UserService
const svc = new UserService(new MySQLDatabase());
svc.saveUser("Alice");
// Easy to test with a mock
const testSvc = new UserService(new MockDatabase());
testSvc.saveUser("TestUser");Creational patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented. There are five classic GoF creational patterns.
| Pattern | Intent | Key Use Case | Complexity |
|---|---|---|---|
| Singleton | Ensure a class has only one instance | Configuration, logging, connection pools | Low |
| Factory Method | Let subclasses decide which class to instantiate | Frameworks, cross-platform UI | Low |
| Abstract Factory | Create families of related objects | UI themes, database drivers | Medium |
| Builder | Step-by-step construction of complex objects | SQL queries, API requests, config objects | Medium |
| Prototype | Clone existing objects without coupling to their classes | Expensive object creation, undo operations | Low |
Ensures a class has only one instance and provides a global point of access to it.
# ── Thread-safe Singleton (Python) ──
from threading import Lock
class DatabaseConnection:
_instance = None
_lock = Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
# Double-checked locking
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._connected = False
return cls._instance
def connect(self):
if not self._connected:
print("Connected to database")
self._connected = True
return self
def query(self, sql):
return f"Executing: {sql}"
# Usage — always the same instance
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True// ── Singleton in TypeScript ──
class Logger {
private static instance: Logger;
private logs: string[] = [];
private constructor() {} // prevent external instantiation
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
log(message: string) {
this.logs.push(message);
console.log(`[${new Date().toISOString()}] ${message}`);
}
getHistory(): string[] {
return [...this.logs];
}
}
// Usage
const logger = Logger.getInstance();
logger.log("App started");
// Decorator-based singleton (modern TS)
function Singleton<T extends new (...args: any[]) => {}>(constructor: T) {
let instance: InstanceType<T>;
return class extends constructor {
constructor(...args: any[]) {
if (instance) return instance;
super(...args);
instance = this as InstanceType<T>;
}
} as any;
}Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
from abc import ABC, abstractmethod
# ── Product interface ──
class Notification(ABC):
@abstractmethod
def send(self, message: str): ...
# ── Concrete products ──
class EmailNotification(Notification):
def send(self, message):
print(f"📧 Email: {message}")
class SMSNotification(Notification):
def send(self, message):
print(f"📱 SMS: {message}")
class PushNotification(Notification):
def send(self, message):
print(f"🔔 Push: {message}")
# ── Factory (with registration pattern) ──
class NotificationFactory:
_creators = {}
@classmethod
def register(cls, type_name: str, creator):
cls._creators[type_name] = creator
@classmethod
def create(cls, type_name: str) -> Notification:
creator = cls._creators.get(type_name)
if not creator:
raise ValueError(f"Unknown type: {type_name}")
return creator()
# Register products
NotificationFactory.register("email", EmailNotification)
NotificationFactory.register("sms", SMSNotification)
NotificationFactory.register("push", PushNotification)
# Usage
notif = NotificationFactory.create("email")
notif.send("Welcome!") # 📧 Email: Welcome!Provides an interface for creating families of related objects without specifying their concrete classes.
// ── Abstract Factory: UI Theme Example ──
interface Button {
render(): string;
}
interface Checkbox {
render(): string;
}
// Mac theme
class MacButton implements Button { render() { return "[Mac Button]"; } }
class MacCheckbox implements Checkbox { render() { return "[Mac Checkbox]"; } }
// Windows theme
class WinButton implements Button { render() { return "(Win Button)"; } }
class WinCheckbox implements Checkbox { render() { return "(Win Checkbox)"; } }
// Abstract Factory interface
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// Concrete factories
class MacFactory implements UIFactory {
createButton() { return new MacButton(); }
createCheckbox() { return new MacCheckbox(); }
}
class WinFactory implements UIFactory {
createButton() { return new WinButton(); }
createCheckbox() { return new WinCheckbox(); }
}
// Client — depends only on abstractions
function createUI(factory: UIFactory) {
const btn = factory.createButton();
const chk = factory.createCheckbox();
console.log(btn.render(), chk.render());
}
createUI(new MacFactory()); // [Mac Button] [Mac Checkbox]
createUI(new WinFactory()); // (Win Button) (Win Checkbox)Separates the construction of a complex object from its representation, so the same construction process can create different representations.
// ── Builder: HTTP Request Example ──
class HttpRequest {
constructor(
public method: string = "GET",
public url: string = "",
public headers: Record<string, string> = {},
public body: string = "",
public timeout: number = 5000,
) {}
}
class HttpRequestBuilder {
private request: HttpRequest = new HttpRequest();
setMethod(method: string): this {
this.request.method = method;
return this;
}
setUrl(url: string): this {
this.request.url = url;
return this;
}
addHeader(key: string, value: string): this {
this.request.headers[key] = value;
return this;
}
setBody(body: string): this {
this.request.body = body;
return this;
}
setTimeout(ms: number): this {
this.request.timeout = ms;
return this;
}
build(): HttpRequest {
if (!this.request.url) throw new Error("URL is required");
return { ...this.request };
}
}
// Usage — fluent interface
const req = new HttpRequestBuilder()
.setMethod("POST")
.setUrl("https://api.example.com/users")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer token123")
.setBody('{ "name": "Alice" }')
.setTimeout(10000)
.build();
console.log(req.method); // POST
console.log(req.url); // https://api.example.com/usersCreates new objects by cloning an existing instance (the prototype) instead of creating from scratch. Useful when creating an object is expensive.
import copy
class Document:
def __init__(self, title, content, fonts=None, margins=None):
self.title = title
self.content = content
self.fonts = fonts or {"body": "Helvetica", "heading": "Arial"}
self.margins = margins or {"top": 72, "bottom": 72}
def clone(self):
# Deep copy to avoid shared mutable state
return copy.deepcopy(self)
def __repr__(self):
return f"Document('{self.title}', {len(self.content)} chars)"
# ── Usage: Clone a template instead of creating from scratch ──
template = Document("Report Template",
"Annual Report for {year}",
fonts={"body": "Times New Roman", "heading": "Georgia"},
margins={"top": 96, "bottom": 96})
# Clone and customize — no expensive initialization
report_2024 = template.clone()
report_2024.title = "2024 Annual Report"
report_2024.content = report_2024.content.replace("{year}", "2024")
report_2025 = template.clone()
report_2025.title = "2025 Annual Report"
report_2025.content = report_2025.content.replace("{year}", "2025")
report_2025.margins["top"] = 72 # independent from templateStructural patterns deal with the composition of classes and objects. They help form larger structures from individual objects while keeping these structures flexible and efficient.
| Pattern | Intent | Key Analogy | Complexity |
|---|---|---|---|
| Adapter | Convert one interface into another | Power plug adapter (US to EU) | Low |
| Bridge | Decouple abstraction from implementation | Remote control + device | Medium |
| Composite | Tree structure of objects | File system (files + folders) | Medium |
| Decorator | Add behavior dynamically | Clothing layers, coffee toppings | Low |
| Facade | Simplified interface to a subsystem | Smart home "away mode" button | Low |
| Flyweight | Share common state efficiently | Text editor character rendering | Medium |
| Proxy | Control access to an object | VPN, lazy loading, access control | Low |
Converts the interface of a class into another interface clients expect. Lets classes work together that couldn't otherwise because of incompatible interfaces.
// ── Adapter: Third-party payment integration ──
// Existing system expects this interface:
interface PaymentProcessor {
processPayment(amount: number): string;
refund(amount: number): string;
}
// Third-party (incompatible) payment SDK:
class StripeSDK {
charge(amountInCents: number): { id: string } {
return { id: `ch_${Date.now()}` };
}
createRefund(chargeId: string): string {
return `re_${Date.now()}`;
}
}
// Adapter bridges the gap
class StripeAdapter implements PaymentProcessor {
constructor(private stripe: StripeSDK) {}
processPayment(amount: number): string {
const result = this.stripe.charge(amount * 100); // USD -> cents
return result.id;
}
refund(amount: number): string {
return this.stripe.createRefund(`ch_${amount}`);
}
}
// Usage — client code works with the standard interface
const processor: PaymentProcessor = new StripeAdapter(new StripeSDK());
processor.processPayment(49.99); // client doesn't know about StripeSDKDecouples an abstraction from its implementation so that the two can vary independently. Prevents a permanent binding between abstraction and implementation.
# ── Bridge: Logging with different output mechanisms ──
from abc import ABC, abstractmethod
# Implementation interface
class LoggerImpl(ABC):
@abstractmethod
def write(self, message: str): ...
class ConsoleLogger(LoggerImpl):
def write(self, message):
print(f"[CONSOLE] {message}")
class FileLogger(LoggerImpl):
def __init__(self, filename):
self.file = open(filename, "a")
def write(self, message):
self.file.write(f"[FILE] {message}\n")
# Abstraction
class Logger(ABC):
def __init__(self, impl: LoggerImpl):
self.impl = impl
def set_impl(self, impl: LoggerImpl):
self.impl = impl
@abstractmethod
def log(self, message: str): ...
class InfoLogger(Logger):
def log(self, message):
self.impl.write(f"INFO: {message}")
class ErrorLogger(Logger):
def log(self, message):
self.impl.write(f"ERROR: {message}")
# Usage — switch implementation at runtime
console = ConsoleLogger()
file_log = FileLogger("app.log")
logger = InfoLogger(console)
logger.log("App started") # [CONSOLE] INFO: App started
logger.set_impl(file_log)
logger.log("Writing to file") # writes to app.logComposes objects into tree structures to represent part-whole hierarchies. Lets clients treat individual objects and compositions uniformly.
// ── Composite: File System ──
interface FileSystemNode {
name: string;
getSize(): number;
print(indent?: string): void;
}
class File implements FileSystemNode {
constructor(public name: string, private size: number) {}
getSize() { return this.size; }
print(indent = "") {
console.log(`${indent}📄 ${this.name} (${this.size}KB)`);
}
}
class Directory implements FileSystemNode {
private children: FileSystemNode[] = [];
constructor(public name: string) {}
add(node: FileSystemNode): this {
this.children.push(node);
return this;
}
getSize(): number {
return this.children.reduce((sum, c) => sum + c.getSize(), 0);
}
print(indent = "") {
console.log(`${indent}📁 ${this.name}/`);
for (const child of this.children) {
child.print(indent + " ");
}
}
}
// Usage — treat files and directories uniformly
const root = new Directory("projects")
.add(new File("readme.md", 4))
.add(new Directory("src")
.add(new File("index.ts", 12))
.add(new File("utils.ts", 8))
)
.add(new Directory("tests")
.add(new File("index.test.ts", 6))
);
root.print();
console.log("Total size:", root.getSize(), "KB");Dynamically attaches additional responsibilities to an object. Provides a flexible alternative to subclassing for extending functionality.
from abc import ABC, abstractmethod
from functools import wraps
import time
# ── Decorator Pattern (OOP style): Coffee Shop ──
class Beverage(ABC):
@abstractmethod
def cost(self) -> float: ...
@abstractmethod
def description(self) -> str: ...
class Espresso(Beverage):
def cost(self): return 2.00
def description(self): return "Espresso"
class CondimentDecorator(Beverage):
def __init__(self, beverage: Beverage):
self.beverage = beverage
class Milk(CondimentDecorator):
def cost(self): return self.beverage.cost() + 0.50
def description(self): return f"{self.beverage.description()} + Milk"
class Mocha(CondimentDecorator):
def cost(self): return self.beverage.cost() + 0.75
def description(self): return f"{self.beverage.description()} + Mocha"
class Whip(CondimentDecorator):
def cost(self): return self.beverage.cost() + 0.25
def description(self): return f"{self.beverage.description()} + Whip"
# Usage — stack decorators
order = Whip(Mocha(Milk(Espresso())))
print(order.description()) # Espresso + Milk + Mocha + Whip
print(f"${order.cost():.2f}") # $3.50// ── Decorator as Middleware (Functional style): Express-like ──
type Middleware = (req: Request, next: () => void) => void;
class Request {
constructor(public url: string, public headers: Record<string, string> = {}) {}
}
function logger(req: Request, next: () => void) {
console.log(`→ ${req.url}`);
next();
console.log(`← ${req.url}`);
}
function auth(req: Request, next: () => void) {
if (req.headers["authorization"]) {
console.log(" ✓ Authenticated");
next();
} else {
console.log(" ✗ Unauthorized");
}
}
function cors(req: Request, next: () => void) {
req.headers["Access-Control-Allow-Origin"] = "*";
next();
}
// Chain decorators dynamically
function applyMiddleware(middlewares: Middleware[], req: Request) {
let index = 0;
function next() {
if (index < middlewares.length) {
middlewares[index++](req, next);
}
}
next();
}
applyMiddleware(
[logger, cors, auth],
new Request("/api/users", { authorization: "Bearer xyz" })
);Provides a simplified interfaceto a complex subsystem. Doesn't encapsulate the subsystem — it just provides a convenient entry point.
// ── Facade: E-commerce order processing ──
class InventoryService {
checkStock(productId: string): boolean {
console.log(`Checking stock for ${productId}`);
return true;
}
reserveItem(productId: string, qty: number) {
console.log(`Reserved ${qty} of ${productId}`);
}
}
class PaymentService {
processPayment(amount: number, method: string): string {
console.log(`Charged $${amount} via ${method}`);
return "txn_12345";
}
}
class ShippingService {
arrangeShipping(address: string) {
console.log(`Shipping to ${address}`);
}
}
class NotificationService {
sendConfirmation(email: string, orderId: string) {
console.log(`Confirmation sent to ${email}`);
}
}
// Facade — one simple interface for the complex subsystem
class OrderFacade {
constructor(
private inventory = new InventoryService(),
private payment = new PaymentService(),
private shipping = new ShippingService(),
private notification = new NotificationService(),
) {}
placeOrder(productId: string, qty: number, address: string, email: string) {
if (!this.inventory.checkStock(productId)) {
console.log("Out of stock!"); return;
}
this.inventory.reserveItem(productId, qty);
this.payment.processPayment(49.99, "credit_card");
this.shipping.arrangeShipping(address);
this.notification.sendConfirmation(email, "ORD-001");
console.log("Order placed successfully!");
}
}
// Usage — client only knows about the facade
new OrderFacade().placeOrder("SKU-001", 2, "123 Main St", "user@mail.com");Minimizes memory usage by sharing common state among many similar objects. Separates intrinsic state (shared) from extrinsic state (unique per instance).
# ── Flyweight: Tree rendering in a forest ──
from dataclasses import dataclass
@dataclass(frozen=True)
class TreeType:
"""Intrinsic state — shared among many trees"""
name: str
color: str
texture: str
class TreeFlyweightFactory:
_pool: dict[tuple, TreeType] = {}
@classmethod
def get_tree_type(cls, name, color, texture):
key = (name, color, texture)
if key not in cls._pool:
cls._pool[key] = TreeType(name, color, texture)
print(f" Created new TreeType: {name}")
else:
print(f" Reused existing TreeType: {name}")
return cls._pool[key]
@classmethod
def pool_size(cls):
return len(cls._pool)
@dataclass
class Tree:
"""Extrinsic state — unique per tree instance"""
x: int
y: int
tree_type: TreeType
def render(self):
print(f" 🌳 {self.tree_type.name} at ({self.x},{self.y}) "
f"[{self.tree_type.color}]")
# Usage — 10,000 trees share only 3 flyweights
forest = [
Tree(x, y, TreeFlyweightFactory.get_tree_type(name, color, tex))
for name, color, tex in [
("Oak", "green", "rough"),
("Pine", "dark-green", "needle"),
("Birch", "light-green", "smooth"),
]
for x, y in [(i * 10, i * 10) for i in range(10)]
]
print(f"Trees: {len(forest)}, Flyweights: {TreeFlyweightFactory.pool_size()}")
# Trees: 30, Flyweights: 3Provides a surrogate or placeholder to control access to an object. Common variants: Virtual Proxy (lazy loading), Protection Proxy (access control), Remote Proxy (network access).
// ── Proxy: Virtual Proxy (Lazy Loading) ──
interface Image {
display(): void;
}
class RealImage implements Image {
constructor(private filename: string) {
this.loadFromDisk();
}
private loadFromDisk() {
console.log(` Loading ${this.filename} from disk...`);
}
display() {
console.log(` Displaying ${this.filename}`);
}
}
class LazyImageProxy implements Image {
private realImage: RealImage | null = null;
constructor(private filename: string) {}
display() {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}
// ── Protection Proxy ──
class ProtectedDocument {
constructor(public content: string) {}
}
class DocumentProxy {
constructor(
private doc: ProtectedDocument,
private userRole: string,
) {}
read(): string {
return this.doc.content;
}
write(newContent: string) {
if (this.userRole !== "admin") {
throw new Error("Access denied: write requires admin role");
}
this.doc.content = newContent;
}
}
// Usage
const images = [
new LazyImageProxy("photo1.jpg"),
new LazyImageProxy("photo2.jpg"),
];
console.log("Images created (not loaded yet)");
images[0].display(); // Loading + Displaying
console.log("Second access (already loaded):");
images[0].display(); // Just DisplayingBehavioral patterns are concerned with algorithms and assignment of responsibilities between objects. They describe patterns of communication between objects and how they distribute work.
| Pattern | Intent | Real-World Analogy | Complexity |
|---|---|---|---|
| Chain of Responsibility | Pass request along a handler chain | Support ticket escalation | Low |
| Command | Encapsulate a request as an object | Restaurant order, remote control | Low |
| Iterator | Sequential access to collection elements | TV channel surfing | Low |
| Mediator | Centralize communication between objects | Air traffic control tower | Medium |
| Memento | Capture/restore object state | Undo/redo, game save | Low |
| Observer | Notify dependents of state changes | Newsletter subscription | Low |
| Strategy | Swap algorithms at runtime | Navigation routes (car/walk/bus) | Low |
| Template Method | Define algorithm skeleton, defer steps | Recipe with customizable steps | Low |
| State | Alter behavior when state changes | Vending machine states | Medium |
| Visitor | Add operations without changing classes | Tax inspector visiting companies | High |
Passes a request along a chain of handlers. Each handler decides to process the request or pass it to the next handler. Decouples sender from receiver.
// ── Chain of Responsibility: Middleware / Request Processing ──
interface Handler {
setNext(handler: Handler): Handler;
handle(request: any): string | null;
}
abstract class AbstractHandler implements Handler {
private nextHandler: Handler | null = null;
setNext(handler: Handler): Handler {
this.nextHandler = handler;
return handler; // allows chaining
}
handle(request: any): string | null {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null; // end of chain
}
}
class AuthHandler extends AbstractHandler {
handle(request: any): string | null {
if (!request.token) return "Authentication failed: no token";
console.log(" ✓ Authenticated");
return super.handle(request);
}
}
class RateLimitHandler extends AbstractHandler {
private count = 0;
handle(request: any): string | null {
this.count++;
if (this.count > 3) return "Rate limit exceeded";
console.log(" ✓ Rate limit OK");
return super.handle(request);
}
}
class LogHandler extends AbstractHandler {
handle(request: any): string | null {
console.log(` 📝 Log: ${request.method} ${request.url}`);
return super.handle(request);
}
}
// Build chain
const auth = new AuthHandler();
const rateLimit = new RateLimitHandler();
const log = new LogHandler();
auth.setNext(rateLimit).setNext(log);
const result = auth.handle({ method: "GET", url: "/api", token: "abc" });
// ✓ Authenticated → ✓ Rate limit OK → 📝 Log: GET /apiEncapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undo/redo.
# ── Command Pattern: Smart Home Remote Control ──
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self): ...
@abstractmethod
def undo(self): ...
class Light:
def __init__(self):
self.on = False
self.brightness = 100
def turn_on(self):
self.on = True; print(" 💡 Light ON")
def turn_off(self):
self.on = False; print(" 💡 Light OFF")
def set_brightness(self, level):
self.brightness = level; print(f" 💡 Brightness: {level}%")
class LightOnCommand(Command):
def __init__(self, light): self.light = light
def execute(self): self.light.turn_on()
def undo(self): self.light.turn_off()
class BrightnessCommand(Command):
def __init__(self, light, level):
self.light = light
self.level = level
self.prev = light.brightness
def execute(self):
self.prev = self.light.brightness
self.light.set_brightness(self.level)
def undo(self):
self.light.set_brightness(self.prev)
class RemoteControl:
def __init__(self):
self.history = []
def press(self, command: Command):
command.execute()
self.history.append(command)
def undo(self):
if self.history:
self.history.pop().undo()
# Usage
light = Light()
remote = RemoteControl()
remote.press(LightOnCommand(light)) # 💡 Light ON
remote.press(BrightnessCommand(light, 70)) # 💡 Brightness: 70%
remote.undo() # 💡 Brightness: 100%
remote.undo() # 💡 Light OFFProvides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
// ── Iterator: Custom collection traversal ──
interface Iterator<T> {
next(): { value: T; done: boolean };
hasNext(): boolean;
}
interface IterableCollection<T> {
createIterator(): Iterator<T>;
}
class TreeNode<T> {
constructor(public value: T, public left?: TreeNode<T>, public right?: TreeNode<T>) {}
}
// In-order traversal iterator for binary tree
class TreeIterator<T> implements Iterator<T> {
private stack: TreeNode<T>[] = [];
constructor(root?: TreeNode<T>) {
this.pushLeft(root);
}
private pushLeft(node?: TreeNode<T>) {
while (node) {
this.stack.push(node);
node = node.left;
}
}
next() {
if (!this.hasNext()) return { value: null as T, done: true };
const node = this.stack.pop()!;
this.pushLeft(node.right);
return { value: node.value, done: false };
}
hasNext() { return this.stack.length > 0; }
}
class BinaryTree<T> implements IterableCollection<T> {
constructor(public root?: TreeNode<T>) {}
createIterator() { return new TreeIterator(this.root); }
}
// Usage
const tree = new BinaryTree(
new TreeNode(4,
new TreeNode(2, new TreeNode(1), new TreeNode(3)),
new TreeNode(6, new TreeNode(5), new TreeNode(7)),
)
);
const iter = tree.createIterator();
let result = iter.next();
while (!result.done) {
console.log(result.value); // 1, 2, 3, 4, 5, 6, 7
result = iter.next();
}Defines an object that centralizes communicationbetween components. Components don't communicate directly — they go through the mediator.
# ── Mediator: Chat Room ──
from abc import ABC, abstractmethod
class Mediator(ABC):
@abstractmethod
def send(self, message: str, sender: "User"): ...
class ChatRoom(Mediator):
def __init__(self):
self.users: list["User"] = []
def register(self, user: "User"):
self.users.append(user)
user.mediator = self
def send(self, message: str, sender: "User"):
for user in self.users:
if user is not sender:
user.receive(message, sender.name)
class User:
def __init__(self, name: str):
self.name = name
self.mediator: Mediator | None = None
def send(self, message: str):
print(f"[{self.name}]: {message}")
if self.mediator:
self.mediator.send(message, self)
def receive(self, message: str, sender_name: str):
print(f" → [{self.name}] received from {sender_name}: {message}")
# Usage — users don't know about each other
room = ChatRoom()
alice = User("Alice")
bob = User("Bob")
charlie = User("Charlie")
for u in [alice, bob, charlie]:
room.register(u)
alice.send("Hi everyone!")
# Bob and Charlie receive the messageCaptures and restores an object's internal state without violating encapsulation. Used for undo/redo, snapshots, and rollback.
// ── Memento: Text Editor Undo/Redo ──
interface Memento<T> {
getState(): T;
getName(): string;
getTimestamp(): Date;
}
class EditorState implements Memento<string> {
constructor(
private state: string,
private name: string,
) {}
getState() { return this.state; }
getName() { return this.name; }
getTimestamp() { return new Date(); }
}
class Editor {
private content = "";
type(text: string) {
this.content += text;
}
save(): Memento<string> {
return new EditorState(this.content, `Snapshot at ${Date.now()}`);
}
restore(m: Memento<string>) {
this.content = m.getState();
}
getContent() { return this.content; }
}
class History {
private mementos: Memento<string>[] = [];
private index = -1;
push(m: Memento<string>) {
// Remove future states when branching from history
this.mementos = this.mementos.slice(0, this.index + 1);
this.mementos.push(m);
this.index++;
}
undo(): Memento<string> | null {
if (this.index > 0) { this.index--; return this.mementos[this.index]; }
return null;
}
redo(): Memento<string> | null {
if (this.index < this.mementos.length - 1) {
this.index++; return this.mementos[this.index];
}
return null;
}
}
// Usage
const editor = new Editor();
const history = new History();
editor.type("Hello");
history.push(editor.save());
editor.type(" World");
history.push(editor.save());
editor.type("!");
console.log(editor.getContent()); // Hello World!
editor.restore(history.undo()!); // Hello World
editor.restore(history.undo()!); // Hello
editor.restore(history.redo()!); // Hello WorldDefines a one-to-many dependency — when one object changes state, all its dependents are notified and updated automatically. Also known as Publish/Subscribe.
// ── Observer: Event Emitter / Pub-Sub ──
type Callback = (data: any) => void;
class EventEmitter {
private events: Map<string, Set<Callback>> = new Map();
on(event: string, cb: Callback): () => void {
if (!this.events.has(event)) this.events.set(event, new Set());
this.events.get(event)!.add(cb);
// Return unsubscribe function
return () => this.events.get(event)?.delete(cb);
}
emit(event: string, data?: any) {
this.events.get(event)?.forEach(cb => cb(data));
}
}
// ── Usage: Stock ticker ──
const ticker = new EventEmitter();
const unsub1 = ticker.on("price:change", (data) => {
console.log(` 📈 [Dashboard] AAPL: $${data.price}`);
});
const unsub2 = ticker.on("price:change", (data) => {
if (data.price > 180) console.log(" ⚠️ [Alert] AAPL above $180!");
});
ticker.on("price:change", (data) => {
console.log(` 📊 [Logger] Price updated: $${data.price}`);
});
ticker.emit("price:change", { price: 175 });
// Dashboard + Logger receive it
ticker.emit("price:change", { price: 185 });
// Dashboard + Logger + Alert receive it
unsub1(); // Unsubscribe dashboard
ticker.emit("price:change", { price: 190 });
// Only Logger + Alert receive itDefines a family of algorithms, encapsulates each one, and makes them interchangeable. Lets the algorithm vary independently from the clients that use it.
# ── Strategy: Shipping cost calculation ──
from abc import ABC, abstractmethod
class ShippingStrategy(ABC):
@abstractmethod
def calculate(self, weight: float, distance: float) -> float: ...
class StandardShipping(ShippingStrategy):
def calculate(self, weight, distance):
return weight * 1.5 + distance * 0.05
class ExpressShipping(ShippingStrategy):
def calculate(self, weight, distance):
return (weight * 2.5 + distance * 0.08) * 1.5
class FreeShipping(ShippingStrategy):
def calculate(self, weight, distance):
return 0.0 # Free for orders over $100
class SameDayShipping(ShippingStrategy):
def calculate(self, weight, distance):
return weight * 5.0 + distance * 0.15 + 25.0
class Order:
def __init__(self, weight, distance, total):
self.weight = weight
self.distance = distance
self.total = total
self._shipping: ShippingStrategy = StandardShipping()
def set_shipping(self, strategy: ShippingStrategy):
self._shipping = strategy
def shipping_cost(self):
return self._shipping.calculate(self.weight, self.distance)
# Usage — swap strategy at runtime
order = Order(5.0, 500, 150)
print(f"Standard: ${order.shipping_cost():.2f}") # $32.50
order.set_shipping(ExpressShipping())
print(f"Express: ${order.shipping_cost():.2f}") # $64.50
order.set_shipping(FreeShipping())
print(f"Free: ${order.shipping_cost():.2f}") # $0.00Defines the skeleton of an algorithmin a base class, deferring some steps to subclasses. Lets subclasses redefine certain steps without changing the algorithm's structure.
# ── Template Method: Data Pipeline Framework ──
from abc import ABC, abstractmethod
class DataPipeline(ABC):
"""Template method defines the algorithm skeleton"""
def run(self):
data = self.extract()
data = self.transform(data)
data = self.validate(data)
self.load(data)
self.cleanup()
@abstractmethod
def extract(self) -> dict: ...
@abstractmethod
def transform(self, data: dict) -> dict: ...
def validate(self, data: dict) -> dict:
"""Hook: default validation (can be overridden)"""
if not data:
raise ValueError("Empty data")
print(" ✓ Validated")
return data
@abstractmethod
def load(self, data: dict): ...
def cleanup(self):
"""Hook: default cleanup (can be overridden)"""
print(" ✓ Cleanup complete")
class CSVPipeline(DataPipeline):
def extract(self):
print(" → Extracting from CSV file")
return {"name": "Alice", "age": 30}
def transform(self, data):
print(" → Normalizing CSV data")
data["name"] = data["name"].upper()
return data
def load(self, data):
print(f" → Loading to database: {data}")
class APIPipeline(DataPipeline):
def extract(self):
print(" → Fetching from REST API")
return {"name": "Bob", "age": 25, "email": "bob@test.com"}
def transform(self, data):
print(" → Parsing JSON response")
return data
def validate(self, data):
"""Override with stricter validation"""
super().validate(data)
if "email" not in data:
raise ValueError("Email required")
return data
def load(self, data):
print(f" → Sending to webhook: {data}")
# Usage
print("=== CSV Pipeline ===")
CSVPipeline().run()
print("\n=== API Pipeline ===")
APIPipeline().run()Allows an object to alter its behavior when its internal state changes. The object will appear to change its class. Eliminates large conditionals based on state.
// ── State Pattern: Vending Machine ──
interface VendingState {
insertCoin(machine: VendingMachine): void;
ejectCoin(machine: VendingMachine): void;
dispense(machine: VendingMachine): void;
}
class VendingMachine {
state: VendingState = new NoCoinState();
stock = 5;
changeState(state: VendingState) { this.state = state; }
insertCoin() { this.state.insertCoin(this); }
ejectCoin() { this.state.ejectCoin(this); }
dispense() { this.state.dispense(this); }
}
class NoCoinState implements VendingState {
insertCoin(m: VendingMachine) {
console.log(" Coin inserted");
m.changeState(new HasCoinState());
}
ejectCoin(m: VendingMachine) {
console.log(" No coin to eject");
}
dispense(m: VendingMachine) {
console.log(" Insert a coin first");
}
}
class HasCoinState implements VendingState {
insertCoin(m: VendingMachine) {
console.log(" Already has a coin");
}
ejectCoin(m: VendingMachine) {
console.log(" Coin ejected");
m.changeState(new NoCoinState());
}
dispense(m: VendingMachine) {
if (m.stock <= 0) {
console.log(" Out of stock!");
m.changeState(new SoldOutState());
return;
}
m.stock--;
console.log(` Item dispensed! Stock: ${m.stock}`);
m.changeState(m.stock > 0 ? new NoCoinState() : new SoldOutState());
}
}
class SoldOutState implements VendingState {
insertCoin(m: VendingMachine) { console.log(" Sold out — coin returned"); }
ejectCoin(m: VendingMachine) { console.log(" No coin to eject"); }
dispense(m: VendingMachine) { console.log(" Sold out"); }
}
// Usage
const vm = new VendingMachine();
vm.insertCoin(); vm.dispense(); // Coin inserted → Item dispensed!
vm.insertCoin(); vm.ejectCoin(); // Coin inserted → Coin ejectedLets you define a new operation without changing the classes of the elements on which it operates. Separates the operation from the object structure.
# ── Visitor: Export shapes to different formats ──
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def accept(self, visitor: "Visitor"): ...
class Visitor(ABC):
@abstractmethod
def visit_circle(self, circle: "Circle"): ...
@abstractmethod
def visit_rectangle(self, rectangle: "Rectangle"): ...
class Circle(Shape):
def __init__(self, radius): self.radius = radius
def accept(self, visitor):
visitor.visit_circle(self)
class Rectangle(Shape):
def __init__(self, w, h): self.w, self.h = w, h
def accept(self, visitor):
visitor.visit_rectangle(self)
# ── Concrete visitors ──
class AreaCalculator(Visitor):
def visit_circle(self, c):
print(f" Circle area: {3.14159 * c.radius ** 2:.2f}")
def visit_rectangle(self, r):
print(f" Rectangle area: {r.w * r.h:.2f}")
class XMLExporter(Visitor):
def visit_circle(self, c):
print(f' <circle radius="{c.radius}" />')
def visit_rectangle(self, r):
print(f' <rectangle width="{r.w}" height="{r.h}" />')
class JSONExporter(Visitor):
def visit_circle(self, c):
print(f' {{ "type": "circle", "radius": {c.radius} }}')
def visit_rectangle(self, r):
print(f' {{ "type": "rect", "w": {r.w}, "h": {r.h} }}')
# Usage — add new operations without changing Shape classes
shapes = [Circle(5), Rectangle(4, 6)]
print("=== Areas ===")
for s in shapes: s.accept(AreaCalculator())
print("\n=== XML ===")
for s in shapes: s.accept(XMLExporter())
print("\n=== JSON ===")
for s in shapes: s.accept(JSONExporter())React-specific design patterns solve common problems in React applications. These patterns have evolved significantly with Hooks and function components, though class-based patterns still exist in legacy codebases.
| Pattern | Purpose | React Era | Complexity |
|---|---|---|---|
| HOC | Reuse logic by wrapping components | Class → Function | Medium |
| Render Props | Share code via function prop | Class → Function | Medium |
| Custom Hooks | Extract reusable stateful logic | Hooks (2019+) | Low |
| Compound Components | Implicit shared state via context | Modern | Medium |
| Provider/Context | Global state without prop drilling | Modern | Low |
| Container/Presentational | Separate logic from UI | Class era | Low |
A function that takes a component and returns a new enhanced component. Used for code reuse, logic abstraction, and cross-cutting concerns (auth, logging, theming).
// ── HOC: withAuth (TypeScript) ──
import { ComponentType } from 'react';
interface AuthProps {
isAuthenticated: boolean;
user: { name: string; role: string } | null;
login: () => void;
logout: () => void;
}
function withAuth<P extends object>(
WrappedComponent: ComponentType<P & AuthProps>
): ComponentType<P> {
return function AuthenticatedComponent(props: P) {
// Simulated auth logic
const isAuthenticated = true;
const user = { name: "Alice", role: "admin" };
const login = () => console.log("Logging in...");
const logout = () => console.log("Logging out...");
if (!isAuthenticated) {
return <div>Please log in to access this page.</div>;
}
return (
<WrappedComponent
{...(props as P)}
isAuthenticated={isAuthenticated}
user={user}
login={login}
logout={logout}
/>
);
};
}
// Usage
interface DashboardProps {
title: string;
}
const Dashboard = ({ title, user }: DashboardProps & AuthProps) => (
<div>
<h1>{title}</h1>
<p>Welcome, {user?.name}!</p>
</div>
);
const ProtectedDashboard = withAuth(Dashboard);A component that receives a function as a child (or prop) that returns React elements. The component calls this function to determine what to render, passing state/data as arguments.
// ── Render Props: Mouse Tracker ──
import { useState, ReactNode } from 'react';
interface MousePosition {
x: number;
y: number;
}
interface MouseTrackerProps {
children: (position: MousePosition) => ReactNode;
}
function MouseTracker({ children }: MouseTrackerProps) {
const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
const handleMouseMove = (e: React.MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
{children(position)}
</div>
);
}
// Usage
function App() {
return (
<MouseTracker>
{({ x, y }) => (
<div>
<h1>Mouse Position</h1>
<p>x: {x}, y: {y}</p>
</div>
)}
</MouseTracker>
);
}
// ── Render Props: Data Fetcher ──
function DataFetcher<T>({
url,
children,
}: {
url: string;
children: (data: T | null, loading: boolean, error: string | null) => ReactNode;
}) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// fetch logic here...
return <>{children(data, loading, error)}</>;
}JavaScript functions that extract and reuse stateful logic from components. Custom hooks call other hooks and can compose together. They are the modern replacement for HOCs and render props.
// ── Custom Hook: useLocalStorage ──
import { useState, useEffect, useCallback } from 'react';
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
const remove = useCallback(() => {
setValue(initialValue);
window.localStorage.removeItem(key);
}, [key, initialValue]);
return [value, setValue, remove] as const;
}
// Usage
const [theme, setTheme, clearTheme] = useLocalStorage("theme", "dark");
// ── Custom Hook: useDebounce ──
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// Usage — search input
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 500);
// Fetch API with debouncedQuery (won't fire on every keystroke)
// ── Custom Hook: useAsync ──
function useAsync<T>(asyncFn: () => Promise<T>) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const execute = useCallback(async () => {
setLoading(true); setError(null);
try {
setData(await asyncFn());
} catch (e) {
setError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}, [asyncFn]);
return { data, loading, error, execute };
}A set of components that work together to form a complete UI. They share implicit state through Context and communicate without direct prop passing. The parent manages state; children declare behavior.
// ── Compound Components: Tabs ──
import { createContext, useContext, useState, ReactNode } from 'react';
// Context for shared state
interface TabsContextType {
activeTab: string;
setActiveTab: (id: string) => void;
}
const TabsContext = createContext<TabsContextType>({
activeTab: "",
setActiveTab: () => {},
});
// Parent component
function Tabs({ children, defaultTab }: {
children: ReactNode; defaultTab: string;
}) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
// TabList — container for tab buttons
function TabList({ children }: { children: ReactNode }) {
return <div style={{ display: "flex", gap: 8 }}>{children}</div>;
}
// Tab — individual clickable tab
function Tab({ id, children }: { id: string; children: ReactNode }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
onClick={() => setActiveTab(id)}
style={{
padding: "8px 16px",
fontWeight: activeTab === id ? "bold" : "normal",
borderBottom: activeTab === id ? "2px solid blue" : "none",
}}
>
{children}
</button>
);
}
// TabPanel — content for active tab
function TabPanel({ id, children }: { id: string; children: ReactNode }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== id) return null;
return <div style={{ padding: 16 }}>{children}</div>;
}
// Usage — declarative and readable
function App() {
return (
<Tabs defaultTab="profile">
<TabList>
<Tab id="profile">Profile</Tab>
<Tab id="settings">Settings</Tab>
<Tab id="notifications">Notifications</Tab>
</TabList>
<TabPanel id="profile">User profile content here</TabPanel>
<TabPanel id="settings">Settings form here</TabPanel>
<TabPanel id="notifications">Notifications list here</TabPanel>
</Tabs>
);
}Uses React's Context API to provide global/shared state to components without prop drilling. The Provider wraps the app and the Context is consumed via useContext.
// ── Provider Pattern: Theme Context ──
import { createContext, useContext, useState, ReactNode, useEffect } from 'react';
type Theme = "light" | "dark";
interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType>({
theme: "dark",
toggleTheme: () => {},
});
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>("dark");
const toggleTheme = () => {
setTheme(prev => prev === "dark" ? "light" : "dark");
};
useEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
return useContext(ThemeContext);
}
// Usage in any component
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header style={{ background: theme === "dark" ? "#333" : "#fff" }}>
<h1>My App</h1>
<button onClick={toggleTheme}>
Switch to {theme === "dark" ? "Light" : "Dark"}
</button>
</header>
);
}
// App.tsx
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}Separates logic from presentation. Container components handle state, data fetching, and business logic. Presentational components focus purely on how things look (receive data via props).
// ── Presentational Component (pure UI, no logic) ──
interface UserListProps {
users: { id: number; name: string; email: string }[];
loading: boolean;
error: string | null;
onRefresh: () => void;
}
function UserList({ users, loading, error, onRefresh }: UserListProps) {
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h2>Users</h2>
<button onClick={onRefresh}>Refresh</button>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
</div>
);
}
// ── Container Component (logic, data fetching) ──
import { useState, useEffect, useCallback } from 'react';
function UserListContainer() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchUsers = useCallback(async () => {
setLoading(true); setError(null);
try {
const res = await fetch("/api/users");
setUsers(await res.json());
} catch (e) {
setError("Failed to load users");
} finally {
setLoading(false);
}
}, []);
useEffect(() => { fetchUsers(); }, [fetchUsers]);
return (
<UserList
users={users}
loading={loading}
error={error}
onRefresh={fetchUsers}
/>
);
}Microservice patterns address challenges in distributed systems: communication, data consistency, resilience, deployment, and observability. These patterns are essential for building robust microservice architectures.
| Pattern | Problem Solved | Category | Key Tool |
|---|---|---|---|
| API Gateway | Single entry point, routing, auth | Edge | Kong, Nginx, AWS API GW |
| Circuit Breaker | Prevent cascading failures | Resilience | Resilience4j, Hystrix |
| Saga | Distributed transactions | Data | Axon, Temporal, Camunda |
| CQRS | Separate read/write models | Data | EventStore, MediatR |
| Event Sourcing | Store events as source of truth | Data | EventStoreDB, Kafka |
| Service Mesh | Service-to-service communication | Infrastructure | Istio, Linkerd, Consul |
| Sidecar | Offload cross-cutting concerns | Infrastructure | Envoy, Dapr |
| Backend for Frontend | Optimized API per client | Edge | GraphQL, BFF services |
A single entry point for all client requests. Handles routing, composition, authentication, rate limiting, and cross-cutting concerns before forwarding to downstream services.
// ── API Gateway: Route aggregation & auth ──
import express from 'express';
const app = express();
// Auth middleware at gateway level
function authenticate(req: any, res: any, next: any) {
const token = req.headers['authorization']?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'No token' });
// Verify JWT...
req.userId = 'user_123';
next();
}
app.use('/api/*', authenticate);
// ── Route: Product (proxy to Product Service) ──
app.get('/api/products/:id', async (req, res) => {
const response = await fetch(
`http://product-service:3001/products/${req.params.id}`
);
const data = await response.json();
res.json(data);
});
// ── BFF: Aggregated response for mobile app ──
app.get('/api/mobile/home', async (req, res) => {
// Fan-out: fetch from multiple services in parallel
const [products, orders, notifications] = await Promise.all([
fetch('http://product-service:3001/products/featured')
.then(r => r.json()),
fetch('http://order-service:3002/orders/recent')
.then(r => r.json()),
fetch('http://notification-service:3003/unread')
.then(r => r.json()),
]);
res.json({
featuredProducts: products.slice(0, 5),
recentOrders: orders.slice(0, 3),
unreadCount: notifications.count,
});
});
// ── Rate Limiting ──
const rateLimiter = new Map<string, number[]>();
function rateLimit(maxRequests: number, windowMs: number) {
return (req: any, res: any, next: any) => {
const key = req.ip;
const now = Date.now();
const timestamps = rateLimiter.get(key) || [];
const recent = timestamps.filter(t => now - t < windowMs);
if (recent.length >= maxRequests) {
return res.status(429).json({ error: 'Too many requests' });
}
recent.push(now);
rateLimiter.set(key, recent);
next();
};
}
app.use(rateLimit(100, 60000)); // 100 req/min per IPPrevents cascading failures by monitoring calls to a downstream service. When failures exceed a threshold, the circuit "opens" and calls fail fast (no network request). After a cooldown, it "half-opens" to test recovery.
enum CircuitState { CLOSED, OPEN, HALF_OPEN }
class CircuitBreaker {
private state: CircuitState = CircuitState.CLOSED;
private failureCount = 0;
private lastFailureTime = 0;
private successThreshold = 3;
private failureThreshold = 5;
private resetTimeout = 30000; // 30s cooldown
constructor(
private name: string,
private fallback: (...args: any[]) => any,
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
// OPEN — fail fast, return fallback
if (this.state === CircuitState.OPEN) {
if (Date.now() - this.lastFailureTime > this.resetTimeout) {
this.state = CircuitState.HALF_OPEN;
console.log(` [{this.name}] Half-open → testing`);
} else {
console.log(` [{this.name}] OPEN → fallback`);
return this.fallback();
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
console.log(` [{this.name}] FAILURE (${this.failureCount})`);
return this.fallback();
}
}
private onSuccess() {
this.failureCount = 0;
if (this.state === CircuitState.HALF_OPEN) {
console.log(` Circuit [${this.name}] → CLOSED (recovered)`);
this.state = CircuitState.CLOSED;
}
}
private onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (
this.failureCount >= this.failureThreshold &&
this.state !== CircuitState.OPEN
) {
console.log(` Circuit [${this.name}] → OPEN`);
this.state = CircuitState.OPEN;
}
}
}
// Usage
const breaker = new CircuitBreaker("PaymentService", () => ({
status: "unavailable", cached: true,
}));
const result = await breaker.execute(() =>
fetch("http://payment-service/pay").then(r => r.json())
);Manages distributed transactions across microservices without 2-phase commit. Each local transaction publishes an event that triggers the next step. Compensating transactions undo work on failure.
# ── Saga: E-commerce order (Orchestrator pattern) ──
from dataclasses import dataclass
from enum import Enum, auto
class OrderStatus(Enum):
PENDING = auto()
INVENTORY_RESERVED = auto()
PAYMENT_PROCESSED = auto()
COMPLETED = auto()
COMPENSATING = auto()
FAILED = auto()
@dataclass
class Order:
id: str
status: OrderStatus = OrderStatus.PENDING
class OrderSaga:
def __init__(self):
self.order = Order("ORD-001")
async def execute(self):
try:
# Step 1: Reserve inventory
await self.reserve_inventory()
self.order.status = OrderStatus.INVENTORY_RESERVED
print(f" ✓ Inventory reserved")
# Step 2: Process payment
await self.process_payment()
self.order.status = OrderStatus.PAYMENT_PROCESSED
print(f" ✓ Payment processed")
# Step 3: Ship order
await self.ship_order()
self.order.status = OrderStatus.COMPLETED
print(f" ✓ Order completed!")
except Exception as e:
print(f" ✗ Step failed: {e}")
await self.compensate()
async def compensate(self):
"""Undo completed steps in reverse order"""
self.order.status = OrderStatus.COMPENSATING
print(" ⚠️ Compensating...")
if self.order.status.value >= OrderStatus.PAYMENT_PROCESSED.value:
await self.refund_payment()
print(" ↩ Payment refunded")
if self.order.status.value >= OrderStatus.INVENTORY_RESERVED.value:
await self.release_inventory()
print(" ↩ Inventory released")
self.order.status = OrderStatus.FAILED
print(" ✗ Order failed (compensated)")
async def reserve_inventory(self): ...
async def release_inventory(self): ...
async def process_payment(self): ...
async def refund_payment(self): ...
async def ship_order(self): ...Separates read models from write models. Commands mutate state; queries read state. Read models can be optimized denormalized views, while write models stay normalized.
// ── CQRS: Todo List (simplified) ──
// Write side (Commands)
interface Command { type: string; payload: any; }
interface TodoItem {
id: string;
title: string;
completed: boolean;
updatedAt: Date;
}
class CommandHandler {
private store: TodoItem[] = [];
handle(command: Command): TodoItem[] {
switch (command.type) {
case "ADD_TODO":
this.store.push({
id: crypto.randomUUID(),
title: command.payload.title,
completed: false,
updatedAt: new Date(),
});
break;
case "TOGGLE_TODO":
const item = this.store.find(t => t.id === command.payload.id);
if (item) item.completed = !item.completed;
break;
case "DELETE_TODO":
this.store = this.store.filter(t => t.id !== command.payload.id);
break;
}
return [...this.store];
}
}
// Read side (Queries) — optimized denormalized view
interface ReadModel {
allTodos: () => TodoItem[];
completedTodos: () => TodoItem[];
pendingTodos: () => TodoItem[];
stats: () => { total: number; completed: number; pending: number };
}
class QueryHandler implements ReadModel {
private view: TodoItem[] = [];
updateView(items: TodoItem[]) {
// Build optimized read views
this.view = items;
}
allTodos() { return this.view; }
completedTodos() {
return this.view.filter(t => t.completed);
}
pendingTodos() {
return this.view.filter(t => !t.completed);
}
stats() {
return {
total: this.view.length,
completed: this.view.filter(t => t.completed).length,
pending: this.view.filter(t => !t.completed).length,
};
}
}
// Usage
const commands = new CommandHandler();
const queries = new QueryHandler();
// Write
const result = commands.handle({ type: "ADD_TODO", payload: { title: "Learn CQRS" } });
queries.updateView(result);
console.log(queries.stats()); // { total: 1, completed: 0, pending: 1 }Instead of storing current state, store a sequence of events that led to the current state. State is rebuilt by replaying events. Provides complete audit trail and time-travel debugging.
# ── Event Sourcing: Bank Account ──
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal
@dataclass
class Event:
timestamp: datetime = field(default_factory=datetime.now)
@dataclass
class AccountOpened(Event):
account_id: str
owner: str
@dataclass
class MoneyDeposited(Event):
amount: float
@dataclass
class MoneyWithdrawn(Event):
amount: float
class BankAccount:
def __init__(self):
self.events: list[Event] = []
self.balance = 0.0
self.owner = ""
self.account_id = ""
def apply(self, event: Event):
"""Apply event to rebuild state"""
if isinstance(event, AccountOpened):
self.account_id = event.account_id
self.owner = event.owner
elif isinstance(event, MoneyDeposited):
self.balance += event.amount
elif isinstance(event, MoneyWithdrawn):
self.balance -= event.amount
self.events.append(event)
def deposit(self, amount: float):
if amount <= 0: raise ValueError("Amount must be positive")
self.apply(MoneyDeposited(amount))
def withdraw(self, amount: float):
if amount > self.balance: raise ValueError("Insufficient funds")
self.apply(MoneyWithdrawn(amount))
def get_history(self):
return self.events
# Usage
account = BankAccount()
account.apply(AccountOpened("ACC-001", "Alice"))
account.deposit(1000)
account.deposit(500)
account.withdraw(200)
print(f"Balance: ${account.balance}") # $1300
print(f"Events: {len(account.events)}") # 4 events
print(f"Audit trail:")
for e in account.events:
print(f" [{e.timestamp:%H:%M:%S}] {e.__class__.__name__}")A Service Mesh provides infrastructure-level features (load balancing, encryption, observability) for service-to-service communication via sidecar proxies deployed alongside each service.
# ── Sidecar Pattern: Service Mesh Architecture ──
# Each service has a sidecar proxy (e.g., Envoy) alongside it
apiVersion: v1
kind: Pod
metadata:
name: order-service
spec:
containers:
# Main application
- name: order-service
image: order-service:latest
ports:
- containerPort: 3002
# Sidecar proxy (Envoy)
- name: envoy-sidecar
image: envoyproxy/envoy:v1.28
ports:
- containerPort: 9901 # admin
- containerPort: 15001 # inbound
- containerPort: 15006 # outbound
# ── Service Mesh Features (via sidecar): ──
# 1. mTLS: All inter-service communication encrypted
# 2. Circuit Breaking: Automatic at proxy level
# 3. Retry/Timeout: Configured per route
# 4. Rate Limiting: Per-service limits
# 5. Observability: Metrics, traces, logs
# 6. Traffic Splitting: Canary deployments, A/B testing
# 7. Load Balancing: Round-robin, least-connections
# ── Popular Service Meshes: ──
# Istio: Most feature-rich, Kubernetes native
# Linkerd: Lightweight, simple, Rust-based
# Consul: Multi-platform, service discovery
# AWS App Mesh: Managed, AWS-nativeCreates dedicated backend services per client type (mobile, web, desktop). Each BFF tailors its API to the specific needs of its frontend, aggregating and adapting downstream responses.
// ── BFF: Mobile-specific API ──
// Mobile needs lightweight responses, paginated feeds, offline support
import express from 'express';
const mobileBff = express();
// Tailored mobile endpoint (fewer fields, smaller payload)
mobileBff.get('/api/mobile/feed', async (req, res) => {
const page = parseInt(req.query.page as string) || 1;
const limit = 10; // Mobile loads fewer items
const [posts, unread] = await Promise.all([
fetch(`http://post-service:3001/posts?page=${page}&limit=${limit}`)
.then(r => r.json()),
fetch(`http://notification-service:3003/unread-count`)
.then(r => r.json()),
]);
// Mobile-optimized response (no markdown, no full content)
res.json({
posts: posts.map((p: any) => ({
id: p.id,
title: p.title, // only title, not full body
preview: p.body.slice(0, 100),
likes: p.likeCount,
comments: p.commentCount,
thumbnail: p.imageUrl,
})),
pagination: { page, limit, hasMore: posts.length === limit },
badges: { unreadNotifications: unread.count },
});
});
// ── BFF: Web-specific API ──
// Web needs richer data, SEO, full content
const webBff = express();
webBff.get('/api/web/feed', async (req, res) => {
const [posts, trending, user] = await Promise.all([
fetch('http://post-service:3001/posts?limit=20').then(r => r.json()),
fetch('http://analytics-service:3004/trending').then(r => r.json()),
fetch('http://user-service:3005/me').then(r => r.json()),
]);
// Web-optimized: full content, SEO metadata, trending sidebar
res.json({ posts, trending, user, seo: { /* meta tags */ } });
});Concurrency patterns address challenges in parallel and concurrent programming: thread safety, resource sharing, task distribution, and synchronization. These patterns are critical for high-performance applications, server architectures, and real-time systems.
| Pattern | Problem | Language Support | Complexity |
|---|---|---|---|
| Producer-Consumer | Decouple data production from consumption | Queue + threads/async | Medium |
| Read-Write Lock | Multiple readers OR one writer | threading.RLock (Python) | Medium |
| Thread Pool | Reuse threads, limit concurrency | concurrent.futures, tokio | Low |
| Observer (Pub/Sub) | One-to-many async notification | asyncio, RxJS, Kafka | Low |
| Mutex/Semaphore | Exclusive access to shared resource | threading.Lock, sync.Mutex | Low |
| Future/Promise | Async result handling | async/await, CompletableFuture | Low |
Decouples data producers from consumers using a queue/buffer. Producers add items; consumers remove and process them. Enables different processing rates and parallelism.
import threading
import queue
import time
import random
# Thread-safe queue acts as the buffer
buffer = queue.Queue(maxsize=10)
stop_event = threading.Event()
def producer(name: str):
"""Produces items at variable rate"""
for i in range(5):
item = f"{name}-item-{i}"
time.sleep(random.uniform(0.1, 0.5)) # variable production rate
buffer.put(item)
print(f" 📤 [{name}] Produced: {item} (queue: {buffer.qsize()})")
print(f" [{name}] Done producing")
def consumer(name: str):
"""Consumes items from the buffer"""
while not stop_event.is_set() or not buffer.empty():
try:
item = buffer.get(timeout=0.5)
time.sleep(random.uniform(0.2, 0.8)) # processing takes time
print(f" 📥 [{name}] Consumed: {item} (queue: {buffer.qsize()})")
buffer.task_done()
except queue.Empty:
continue
print(f" [{name}] Done consuming")
# Start multiple producers and consumers
threads = [
threading.Thread(target=producer, args=("P1",)),
threading.Thread(target=producer, args=("P2",)),
threading.Thread(target=consumer, args=("C1",)),
threading.Thread(target=consumer, args=("C2",)),
]
for t in threads: t.start()
for t in threads: t.join()
stop_event.set()
buffer.join()
print("All done!")Allows multiple concurrent readers OR one exclusive writer. Improves performance over a simple mutex when reads are frequent and writes are rare.
import threading
import time
class ReadWriteLock:
"""Custom Read-Write Lock implementation"""
def __init__(self):
self._readers = 0
self._writer = False
self._lock = threading.Lock() # protects internal state
self._read_ready = threading.Condition(self._lock)
self._write_ready = threading.Condition(self._lock)
def acquire_read(self):
with self._read_ready:
while self._writer:
self._read_ready.wait()
self._readers += 1
def release_read(self):
with self._read_ready:
self._readers -= 1
if self._readers == 0:
self._read_ready.notify_all()
def acquire_write(self):
with self._write_ready:
while self._writer or self._readers > 0:
self._write_ready.wait()
self._writer = True
def release_write(self):
with self._write_ready:
self._writer = False
self._write_ready.notify_all()
self._read_ready.notify_all()
# Usage: Thread-safe cache
class ThreadSafeCache:
def __init__(self):
self._data = {}
self._rwlock = ReadWriteLock()
def get(self, key: str):
self._rwlock.acquire_read()
try:
return self._data.get(key)
finally:
self._rwlock.release_read()
def set(self, key: str, value):
self._rwlock.acquire_write()
try:
self._data[key] = value
finally:
self._rwlock.release_write()
cache = ThreadSafeCache()
# Multiple threads can read concurrently
# Only one thread can write (blocks all readers)}Reuses a fixed set of threads instead of creating/destroying threads per task. Tasks are submitted to a queue and picked up by available threads. Limits resource consumption.
import concurrent.futures
import time
import urllib.request
def fetch_url(url: str) -> tuple[str, int]:
"""Fetch a URL and return (url, status_code)"""
start = time.time()
try:
resp = urllib.request.urlopen(url, timeout=5)
return (url, resp.status, time.time() - start)
except Exception as e:
return (url, -1, time.time() - start)
# ── Using ThreadPoolExecutor ──
urls = [
"https://httpbin.org/get",
"https://httpbin.org/delay/1",
"https://httpbin.org/status/200",
"https://httpbin.org/status/404",
"https://httpbin.org/json",
]
print("=== Thread Pool (max 3 concurrent) ===")
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
# Submit all tasks
futures = {pool.submit(fetch_url, url): url for url in urls}
# Process results as they complete
for future in concurrent.futures.as_completed(futures):
url, status, elapsed = future.result()
print(f" {status} {url} ({elapsed:.2f}s)")
# ── Using ProcessPoolExecutor (CPU-bound) ──
def compute_factorial(n: int) -> int:
result = 1
for i in range(2, n + 1):
result *= i
return result
print("\n=== Process Pool (CPU-bound) ===")
with concurrent.futures.ProcessPoolExecutor() as pool:
numbers = [10000, 20000, 30000, 40000, 50000]
results = list(pool.map(compute_factorial, numbers))
print(f" Computed {len(results)} factorials")Asynchronous publish-subscribe pattern where publishers emit events and subscribers receive them independently. Decouples event producers from consumers with message brokers.
// ── Async Pub/Sub with backpressure ──
type Listener<T> = (data: T) => Promise<void> | void;
class AsyncEventBus<T = any> {
private subscribers = new Map<string, Set<Listener<T>>>();
private queue: Array<{ event: string; data: T }> = [];
private processing = false;
subscribe(event: string, listener: Listener<T>): () => void {
if (!this.subscribers.has(event)) {
this.subscribers.set(event, new Set());
}
this.subscribers.get(event)!.add(listener);
return () => this.subscribers.get(event)?.delete(listener);
}
async publish(event: string, data: T) {
this.queue.push({ event, data });
if (!this.processing) {
this.processing = true;
await this.drain();
}
}
private async drain() {
while (this.queue.length > 0) {
const { event, data } = this.queue.shift()!;
const listeners = this.subscribers.get(event);
if (listeners) {
// Notify all subscribers concurrently
await Promise.allSettled(
[...listeners].map(async (fn) => {
try { await fn(data); }
catch (err) { console.error(`Subscriber error:`, err); }
})
);
}
}
this.processing = false;
}
}
// Usage
const bus = new AsyncEventBus<{ price: number; symbol: string }>();
bus.subscribe("trade", async (data) => {
console.log(` 📊 Analytics: ${data.symbol} @ $${data.price}`);
});
bus.subscribe("trade", async (data) => {
if (data.price > 180) console.log(` ⚠️ Alert: ${data.symbol} high!`);
});
await bus.publish("trade", { symbol: "AAPL", price: 175 });
await bus.publish("trade", { symbol: "AAPL", price: 185 });A Mutex (Mutual Exclusion) ensures only one thread accesses a resource at a time. A Semaphore allows N concurrent accesses (generalized mutex where N=1).
import threading
import time
# ── Mutex: Protect shared counter ──
class ThreadSafeCounter:
def __init__(self):
self._value = 0
self._lock = threading.Lock()
def increment(self):
with self._lock: # acquire on enter, release on exit
old = self._value
time.sleep(0.001) # simulate work (no lock = race condition)
self._value = old + 1
@property
def value(self):
return self._value
counter = ThreadSafeCounter()
threads = [threading.Thread(target=counter.increment) for _ in range(100)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter (with mutex): {counter.value}") # Always 100
# ── Semaphore: Limit concurrent DB connections ──
from concurrent.futures import ThreadPoolExecutor
class ConnectionPool:
def __init__(self, max_connections: int = 3):
self._semaphore = threading.Semaphore(max_connections)
self._active = 0
def acquire(self):
self._semaphore.acquire()
self._active += 1
print(f" ↗ Connection acquired ({self._active} active)")
def release(self):
self._active -= 1
print(f" ↘ Connection released ({self._active} active)")
self._semaphore.release()
pool = ConnectionPool(max_connections=3)
def query(name: str):
pool.acquire()
try:
time.sleep(1) # simulate DB query
print(f" ✓ {name} completed")
finally:
pool.release()
# Only 3 concurrent connections allowed
with ThreadPoolExecutor(max_workers=6) as executor:
executor.map(query, [f"Q{i}" for i in range(6)])A Future represents a value that may not be available yet. A Promise is a writable handle to a future — the producer resolves it; the consumer awaits it. Modern languages use async/await.
// ── Future/Promise: Compositional async workflows ──
// Basic Promise
function fetchUser(id: string): Promise<{ name: string; email: string }> {
return new Promise((resolve) => {
setTimeout(() => resolve({ name: "Alice", email: "alice@test.com" }), 100);
});
}
function fetchOrders(userId: string): Promise<string[]> {
return new Promise((resolve) => {
setTimeout(() => resolve(["ORD-1", "ORD-2", "ORD-3"]), 200);
});
}
// ── Chaining (sequencing) ──
fetchUser("123")
.then(user => {
console.log(` User: ${user.name}`);
return fetchOrders(user.email);
})
.then(orders => console.log(` Orders: ${orders.join(", ")}`))
.catch(err => console.error(` Error: ${err}`));
// ── async/await (cleaner) ──
async function getUserOrders() {
try {
const user = await fetchUser("123");
const orders = await fetchOrders(user.email);
console.log(` ${user.name}: ${orders.length} orders`);
return { user, orders };
} catch (error) {
console.error("Failed:", error);
throw error;
}
}
// ── Parallel execution ──
async function loadDashboard() {
// All run in parallel — total time = max(individual times)
const [user, orders, notifications] = await Promise.all([
fetchUser("123"),
fetchOrders("123"),
new Promise(resolve => setTimeout(() => resolve(5), 150)),
]);
console.log(" Dashboard loaded in parallel");
}
// ── Error-tolerant parallel ──
async function loadWithTolerance() {
const results = await Promise.allSettled([
fetchUser("123"),
fetchOrders("bad-id"), // might fail
Promise.reject("Network error"),
]);
for (const r of results) {
if (r.status === "fulfilled") console.log(` ✓ ${r.value}`);
else console.log(` ✗ ${r.reason}`);
}
}
// ── Race (first to complete wins) ──
async function fetchWithTimeout(promise: Promise<any>, ms: number) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), ms)
);
return Promise.race([promise, timeout]);
}