Back to blog

Essential Design Patterns for JavaScript and TypeScript Applications

12 min readBy Mustafa Akkaya

Design patterns are proven solutions to recurring problems in software design. They provide a shared vocabulary for developers and make code more maintainable and scalable. Let's explore essential patterns with practical JavaScript and TypeScript implementations.

Why Design Patterns Matter

Design patterns offer several benefits:

- Reusability - Proven solutions to common problems

- Maintainability - Well-structured, readable code

- Communication - Shared vocabulary among developers

- Scalability - Flexible architecture for growth

- Best Practices - Industry-standard approaches

Creational Patterns

Patterns for object creation mechanisms.

Singleton Pattern

Ensures a class has only one instance with global access.

app.ts

class="keyword">class="comment">// database.ts

class="keyword">class Database {

class="keyword">private class="keyword">static instance: Database;

class="keyword">private connection: any;

class="keyword">private constructor() {

class="keyword">class="comment">// Private constructor prevents direct instantiation

class="keyword">this.connection = class="keyword">this.connect();

}

class="keyword">private connect() {

console.log(class="keyword">class="string">"Connecting to database...");

class="keyword">class="keyword">return {

class="keyword">class="comment">/* connection object */

};

}

class="keyword">public class="keyword">static getInstance(): Database {

class="keyword">class="keyword">if (!Database.instance) {

Database.instance = class="keyword">new Database();

}

class="keyword">class="keyword">return Database.instance;

}

class="keyword">public query(sql: string) {

class="keyword">class="keyword">return class="keyword">this.connection.query(sql);

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const db1 = Database.getInstance();

class="keyword">class="keyword">const db2 = Database.getInstance();

console.log(db1 === db2); class="keyword">class="comment">// true - same instance

Modern Alternative with ES Modules:

app.ts

class="keyword">class="comment">// database.ts

class="keyword">class DatabaseConnection {

class="keyword">private connection: any;

constructor() {

class="keyword">this.connection = class="keyword">this.connect();

}

class="keyword">private connect() {

console.log(class="keyword">class="string">"Connecting to database...");

class="keyword">class="keyword">return {

class="keyword">class="comment">/* connection object */

};

}

class="keyword">public query(sql: string) {

class="keyword">class="keyword">return class="keyword">this.connection.query(sql);

}

}

class="keyword">class="comment">// Export single instance

class="keyword">export class="keyword">class="keyword">const db = class="keyword">new DatabaseConnection();

class="keyword">class="comment">// Usage in other files

class="keyword">import { db } class="keyword">from class="keyword">class="string">"./database";

db.query(class="keyword">class="string">"class="keyword">SELECT * class="keyword">FROM users");

Factory Pattern

Creates objects without specifying exact class.

app.ts

class="keyword">class="comment">// notification-factory.ts

class="keyword">interface Notification {

send(message: string): class="keyword">void

}

class="keyword">class EmailNotification class="keyword">implements Notification {

send(message: string) {

console.log(class="keyword">class="string">Sending email: ${message})

}

}

class="keyword">class SMSNotification class="keyword">implements Notification {

send(message: string) {

console.log(class="keyword">class="string">Sending SMS: ${message})

}

class="keyword">class PushNotification class="keyword">implements Notification {

send(message: string) {

console.log(class="keyword">class="string">Sending push notification: ${message})

}

}

class="keyword">class SlackNotification class="keyword">implements Notification {

send(message: string) {

console.log(class="keyword">class="string">Sending Slack message: ${message})

}

}

class="keyword">type NotificationType = class="keyword">class="string">'email' | class="keyword">class="string">'sms' | class="keyword">class="string">'push' | class="keyword">class="string">'slack'

class="keyword">class NotificationFactory {

class="keyword">static createNotification(class="keyword">type: NotificationType): Notification {

class="keyword">switch (class="keyword">type) {

class="keyword">case class="keyword">class="string">'email':

class="keyword">class="keyword">return class="keyword">new EmailNotification()

class="keyword">case class="keyword">class="string">'sms':

class="keyword">class="keyword">return class="keyword">new SMSNotification()

class="keyword">case class="keyword">class="string">'push':

class="keyword">class="keyword">return class="keyword">new PushNotification()

class="keyword">case class="keyword">class="string">'slack':

class="keyword">class="keyword">return class="keyword">new SlackNotification()

class="keyword">default:

class="keyword">throw class="keyword">new Error(class="keyword">class="string">Unknown notification class="keyword">type: ${class="keyword">type})

}

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const emailNotif = NotificationFactory.createNotification(class="keyword">class="string">'email')

emailNotif.send(class="keyword">class="string">'Welcome to our platform!')

class="keyword">class="keyword">const smsNotif = NotificationFactory.createNotification(class="keyword">class="string">'sms')

smsNotif.send(class="keyword">class="string">'Your code is 123456')

Builder Pattern

Constructs complex objects step by step.

app.ts

class="keyword">class="comment">// query-builder.ts

class="keyword">class QueryBuilder {

class="keyword">private query: string = class="keyword">class="string">"";

class="keyword">private conditions: string[] = [];

class="keyword">private orderByClause: string = class="keyword">class="string">"";

class="keyword">private limitValue: number | class="keyword">null = class="keyword">null;

select(fields: string[]) {

class="keyword">this.query = class="keyword">class="string">class="keyword">SELECT ${fields.join(", ")};

class="keyword">class="keyword">return class="keyword">this;

}

class="keyword">from(table: string) {

class="keyword">this.query += class="keyword">class="string"> class="keyword">FROM ${table};

class="keyword">class="keyword">return class="keyword">this;

}

where(condition: string) {

class="keyword">this.conditions.push(condition);

class="keyword">class="keyword">return class="keyword">this;

}

and(condition: string) {

class="keyword">this.conditions.push(condition);

class="keyword">class="keyword">return class="keyword">this;

}

orderBy(field: string, direction: class="keyword">class="string">"ASC" | class="keyword">class="string">"DESC" = class="keyword">class="string">"ASC") {

class="keyword">this.orderByClause = class="keyword">class="string"> class="keyword">ORDER class="keyword">BY ${field} ${direction};

class="keyword">class="keyword">return class="keyword">this;

}

limit(count: number) {

class="keyword">this.limitValue = count;

class="keyword">class="keyword">return class="keyword">this;

}

build(): string {

class="keyword">class="keyword">let finalQuery = class="keyword">this.query;

class="keyword">class="keyword">if (class="keyword">this.conditions.length > 0) {

finalQuery += class="keyword">class="string"> class="keyword">WHERE ${class="keyword">this.conditions.join(" class="keyword">AND ")};

}

class="keyword">class="keyword">if (class="keyword">this.orderByClause) {

finalQuery += class="keyword">this.orderByClause;

}

class="keyword">class="keyword">if (class="keyword">this.limitValue) {

finalQuery += class="keyword">class="string"> LIMIT ${class="keyword">this.limitValue};

}

class="keyword">class="keyword">return finalQuery;

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const query = class="keyword">new QueryBuilder()

.select([class="keyword">class="string">"id", class="keyword">class="string">"name", class="keyword">class="string">"email"])

.class="keyword">from(class="keyword">class="string">"users")

.where(class="keyword">class="string">"age > 18")

.and(class="keyword">class="string">'country = "US"')

.orderBy(class="keyword">class="string">"created_at", class="keyword">class="string">"DESC")

.limit(10)

.build();

console.log(query);

class="keyword">class="comment">// class="keyword">SELECT id, name, email class="keyword">FROM users class="keyword">WHERE age > 18 class="keyword">AND country = class="keyword">class="string">"US" class="keyword">ORDER class="keyword">BY created_at DESC LIMIT 10

Structural Patterns

Patterns for composing objects and classes.

Adapter Pattern

Allows incompatible interfaces to work together.

app.ts

class="keyword">class="comment">// payment-adapter.ts

class="keyword">interface PaymentProcessor {

processPayment(amount: number): Promise;

}

class="keyword">class="comment">// Third-party Stripe API(incompatible class="keyword">interface)

class="keyword">class StripeAPI {

class="keyword">class="keyword">async charge(cents: number, currency: string) {

console.log(class="keyword">class="string">Charging ${cents} ${currency} via Stripe);

class="keyword">class="keyword">return { success: true, id: class="keyword">class="string">"stripe_123" };

}

}

class="keyword">class="comment">// Third-party PayPal API(incompatible class="keyword">interface)

class="keyword">class PayPalAPI {

class="keyword">class="keyword">async makePayment(dollars: number) {

console.log(class="keyword">class="string">Processing $${dollars} via PayPal);

class="keyword">class="keyword">return { status: class="keyword">class="string">"completed", transactionId: class="keyword">class="string">"pp_456" };

}

}

class="keyword">class="comment">// Adapters

class="keyword">class StripeAdapter class="keyword">implements PaymentProcessor {

class="keyword">private stripe = class="keyword">new StripeAPI();

class="keyword">class="keyword">async processPayment(amount: number): Promise {

class="keyword">class="keyword">const result = class="keyword">class="keyword">await class="keyword">this.stripe.charge(amount * 100, class="keyword">class="string">"USD");

class="keyword">class="keyword">return result.success;

}

}

class="keyword">class PayPalAdapter class="keyword">implements PaymentProcessor {

class="keyword">private paypal = class="keyword">new PayPalAPI();

class="keyword">class="keyword">async processPayment(amount: number): Promise {

class="keyword">class="keyword">const result = class="keyword">class="keyword">await class="keyword">this.paypal.makePayment(amount);

class="keyword">class="keyword">return result.status === class="keyword">class="string">"completed";

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">async class="keyword">class="keyword">function checkout(processor: PaymentProcessor, amount: number) {

class="keyword">class="keyword">const success = class="keyword">class="keyword">await processor.processPayment(amount);

console.log(success ? class="keyword">class="string">"Payment successful" : class="keyword">class="string">"Payment failed");

}

class="keyword">class="keyword">await checkout(class="keyword">new StripeAdapter(), 50);

class="keyword">class="keyword">await checkout(class="keyword">new PayPalAdapter(), 100);

Decorator Pattern

Adds new functionality to objects dynamically.

app.ts

class="keyword">class="comment">// logger-decorator.ts

class="keyword">interface Component {

operation(): string;

}

class="keyword">class ConcreteComponent class="keyword">implements Component {

operation(): string {

class="keyword">class="keyword">return class="keyword">class="string">"ConcreteComponent";

}

}

class="keyword">class="comment">// Base Decorator

abstract class="keyword">class Decorator class="keyword">implements Component {

class="keyword">protected component: Component;

constructor(component: Component) {

class="keyword">this.component = component;

}

operation(): string {

class="keyword">class="keyword">return class="keyword">this.component.operation();

}

}

class="keyword">class="comment">// Concrete Decorators

class="keyword">class LoggerDecorator class="keyword">extends Decorator {

operation(): string {

class="keyword">class="keyword">const result = class="keyword">super.operation();

console.log(class="keyword">class="string">Logging: ${result});

class="keyword">class="keyword">return result;

}

}

class="keyword">class TimerDecorator class="keyword">extends Decorator {

operation(): string {

console.time(class="keyword">class="string">"operation");

class="keyword">class="keyword">const result = class="keyword">super.operation();

console.timeEnd(class="keyword">class="string">"operation");

class="keyword">class="keyword">return result;

}

}

class="keyword">class ErrorHandlerDecorator class="keyword">extends Decorator {

operation(): string {

class="keyword">try {

class="keyword">class="keyword">return class="keyword">super.operation();

} class="keyword">catch (error) {

console.error(class="keyword">class="string">"Error occurred:", error);

class="keyword">class="keyword">return class="keyword">class="string">"Error handled";

}

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">let component: Component = class="keyword">new ConcreteComponent();

component = class="keyword">new LoggerDecorator(component);

component = class="keyword">new TimerDecorator(component);

component = class="keyword">new ErrorHandlerDecorator(component);

component.operation();

Modern Function Decorators (TypeScript):

app.ts

class="keyword">class="comment">// method-decorators.ts

class="keyword">class="keyword">function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {

class="keyword">class="keyword">const originalMethod = descriptor.value;

descriptor.value = class="keyword">class="keyword">function (...args: any[]) {

console.log(class="keyword">class="string">Calling ${propertyKey} with args:, args);

class="keyword">class="keyword">const result = originalMethod.apply(class="keyword">this, args);

console.log(class="keyword">class="string">Result:, result);

class="keyword">class="keyword">return result;

};

class="keyword">class="keyword">return descriptor;

}

class="keyword">class="keyword">function Measure(

target: any,

propertyKey: string,

descriptor: PropertyDescriptor

) {

class="keyword">class="keyword">const originalMethod = descriptor.value;

descriptor.value = class="keyword">class="keyword">async class="keyword">class="keyword">function (...args: any[]) {

console.time(propertyKey);

class="keyword">class="keyword">const result = class="keyword">class="keyword">await originalMethod.apply(class="keyword">this, args);

console.timeEnd(propertyKey);

class="keyword">class="keyword">return result;

};

class="keyword">class="keyword">return descriptor;

}

class="keyword">class UserService {

@Log

@Measure

class="keyword">class="keyword">async getUser(id: string) {

class="keyword">class="comment">// Simulate API call

class="keyword">class="keyword">await class="keyword">new Promise((resolve) => setTimeout(resolve, 100));

class="keyword">class="keyword">return { id, name: class="keyword">class="string">"John Doe" };

}

}

Proxy Pattern

Controls access to an object.

app.ts

class="keyword">class="comment">// api-proxy.ts

class="keyword">interface API {

request(endpoint: string): Promise;

}

class="keyword">class RealAPI class="keyword">implements API {

class="keyword">class="keyword">async request(endpoint: string): Promise {

console.log(class="keyword">class="string">Fetching: ${endpoint});

class="keyword">class="keyword">const response = class="keyword">class="keyword">await fetch(endpoint);

class="keyword">class="keyword">return response.json();

}

}

class="keyword">class CachedAPIProxy class="keyword">implements API {

class="keyword">private realAPI: RealAPI;

class="keyword">private cache = class="keyword">new Map();

class="keyword">private cacheDuration = 5 * 60 * 1000; class="keyword">class="comment">// 5 minutes

constructor() {

class="keyword">this.realAPI = class="keyword">new RealAPI();

}

class="keyword">class="keyword">async request(endpoint: string): Promise {

class="keyword">class="keyword">const cached = class="keyword">this.cache.get(endpoint);

class="keyword">class="keyword">if (cached && Date.now() - cached.timestamp < class="keyword">this.cacheDuration) {

console.log(class="keyword">class="string">Cache hit: ${endpoint});

class="keyword">class="keyword">return cached.data;

}

console.log(class="keyword">class="string">Cache miss: ${endpoint});

class="keyword">class="keyword">const data = class="keyword">class="keyword">await class="keyword">this.realAPI.request(endpoint);

class="keyword">this.cache.set(endpoint, {

data,

timestamp: Date.now(),

});

class="keyword">class="keyword">return data;

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const api = class="keyword">new CachedAPIProxy();

class="keyword">class="keyword">await api.request(class="keyword">class="string">"/api/users"); class="keyword">class="comment">// Cache miss

class="keyword">class="keyword">await api.request(class="keyword">class="string">"/api/users"); class="keyword">class="comment">// Cache hit

Behavioral Patterns

Patterns for communication between objects.

Observer Pattern

Defines one-to-many dependency between objects.

app.ts

class="keyword">class="comment">// event-emitter.ts

class="keyword">interface Observer {

update(data: any): class="keyword">void;

}

class="keyword">class Subject {

class="keyword">private observers: Observer[] = [];

attach(observer: Observer): class="keyword">void {

class="keyword">this.observers.push(observer);

}

detach(observer: Observer): class="keyword">void {

class="keyword">class="keyword">const index = class="keyword">this.observers.indexOf(observer);

class="keyword">class="keyword">if (index > -1) {

class="keyword">this.observers.splice(index, 1);

}

}

notify(data: any): class="keyword">void {

class="keyword">class="keyword">for (class="keyword">class="keyword">const observer of class="keyword">this.observers) {

observer.update(data);

}

}

}

class="keyword">class="comment">// Concrete Observers

class="keyword">class EmailObserver class="keyword">implements Observer {

update(data: any): class="keyword">void {

console.log(class="keyword">class="string">Sending email notification: ${data.message});

}

}

class="keyword">class SMSObserver class="keyword">implements Observer {

update(data: any): class="keyword">void {

console.log(class="keyword">class="string">Sending SMS notification: ${data.message});

}

}

class="keyword">class LogObserver class="keyword">implements Observer {

update(data: any): class="keyword">void {

console.log(class="keyword">class="string">Logging event: ${JSON.stringify(data)});

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const orderSubject = class="keyword">new Subject();

orderSubject.attach(class="keyword">new EmailObserver());

orderSubject.attach(class="keyword">new SMSObserver());

orderSubject.attach(class="keyword">new LogObserver());

orderSubject.notify({ message: class="keyword">class="string">"Order #123 has been shipped" });

Modern Implementation with EventEmitter:

app.ts

class="keyword">class="comment">// modern-event-emitter.ts

class="keyword">type EventHandler = (data: any) => class="keyword">void;

class="keyword">class EventEmitter {

class="keyword">private events = class="keyword">new Map();

on(event: string, handler: EventHandler): class="keyword">void {

class="keyword">class="keyword">if (!class="keyword">this.events.has(event)) {

class="keyword">this.events.set(event, []);

}

class="keyword">this.events.get(event)!.push(handler);

}

off(event: string, handler: EventHandler): class="keyword">void {

class="keyword">class="keyword">const handlers = class="keyword">this.events.get(event);

class="keyword">class="keyword">if (handlers) {

class="keyword">class="keyword">const index = handlers.indexOf(handler);

class="keyword">class="keyword">if (index > -1) {

handlers.splice(index, 1);

}

}

}

emit(event: string, data: any): class="keyword">void {

class="keyword">class="keyword">const handlers = class="keyword">this.events.get(event);

class="keyword">class="keyword">if (handlers) {

handlers.forEach((handler) => handler(data));

}

}

once(event: string, handler: EventHandler): class="keyword">void {

class="keyword">class="keyword">const onceHandler = (data: any) => {

handler(data);

class="keyword">this.off(event, onceHandler);

};

class="keyword">this.on(event, onceHandler);

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const emitter = class="keyword">new EventEmitter();

emitter.on(class="keyword">class="string">"user:login", (user) => {

console.log(class="keyword">class="string">User logged in: ${user.name});

});

emitter.on(class="keyword">class="string">"user:login", (user) => {

console.log(class="keyword">class="string">Updating last login time class="keyword">class="keyword">for ${user.id});

});

emitter.emit(class="keyword">class="string">"user:login", { id: class="keyword">class="string">"123", name: class="keyword">class="string">"John" });

Strategy Pattern

Defines a family of algorithms and makes them interchangeable.

app.ts

class="keyword">class="comment">// payment-strategy.ts

class="keyword">interface PaymentStrategy {

pay(amount: number): class="keyword">void;

}

class="keyword">class CreditCardPayment class="keyword">implements PaymentStrategy {

constructor(class="keyword">private cardNumber: string, class="keyword">private cvv: string) {}

pay(amount: number): class="keyword">void {

console.log(

Paying $${amount} with credit card ending in ${class="keyword">this.cardNumber.slice(

-4

)}

);

}

}

class="keyword">class PayPalPayment class="keyword">implements PaymentStrategy {

constructor(class="keyword">private email: string) {}

pay(amount: number): class="keyword">void {

console.log(class="keyword">class="string">Paying $${amount} with PayPal account ${class="keyword">this.email});

}

}

class="keyword">class CryptoPayment class="keyword">implements PaymentStrategy {

constructor(class="keyword">private walletAddress: string) {}

pay(amount: number): class="keyword">void {

console.log(class="keyword">class="string">Paying $${amount} with crypto wallet ${class="keyword">this.walletAddress});

}

}

class="keyword">class ShoppingCart {

class="keyword">private items: any[] = [];

class="keyword">private paymentStrategy: PaymentStrategy | class="keyword">null = class="keyword">null;

addItem(item: any): class="keyword">void {

class="keyword">this.items.push(item);

}

setPaymentStrategy(strategy: PaymentStrategy): class="keyword">void {

class="keyword">this.paymentStrategy = strategy;

}

checkout(): class="keyword">void {

class="keyword">class="keyword">const total = class="keyword">this.items.reduce((sum, item) => sum + item.price, 0);

class="keyword">class="keyword">if (!class="keyword">this.paymentStrategy) {

class="keyword">throw class="keyword">new Error(class="keyword">class="string">"Payment strategy not set");

}

class="keyword">this.paymentStrategy.pay(total);

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const cart = class="keyword">new ShoppingCart();

cart.addItem({ name: class="keyword">class="string">"Laptop", price: 999 });

cart.addItem({ name: class="keyword">class="string">"Mouse", price: 29 });

cart.setPaymentStrategy(class="keyword">new CreditCardPayment(class="keyword">class="string">"1234567890123456", class="keyword">class="string">"123"));

cart.checkout();

cart.setPaymentStrategy(class="keyword">new PayPalPayment(class="keyword">class="string">"user@example.com"));

cart.checkout();

Command Pattern

Encapsulates a request as an object.

app.ts

class="keyword">class="comment">// command-pattern.ts

class="keyword">interface Command {

execute(): class="keyword">void;

undo(): class="keyword">void;

}

class="keyword">class TextEditor {

class="keyword">private content: string = class="keyword">class="string">"";

write(text: string): class="keyword">void {

class="keyword">this.content += text;

}

delete(length: number): class="keyword">void {

class="keyword">this.content = class="keyword">this.content.slice(0, -length);

}

getContent(): string {

class="keyword">class="keyword">return class="keyword">this.content;

}

}

class="keyword">class WriteCommand class="keyword">implements Command {

class="keyword">private previousContent: string;

constructor(class="keyword">private editor: TextEditor, class="keyword">private text: string) {

class="keyword">this.previousContent = editor.getContent();

}

execute(): class="keyword">void {

class="keyword">this.editor.write(class="keyword">this.text);

}

undo(): class="keyword">void {

class="keyword">class="keyword">const currentLength = class="keyword">this.editor.getContent().length;

class="keyword">class="keyword">const previousLength = class="keyword">this.previousContent.length;

class="keyword">this.editor.delete(currentLength - previousLength);

}

}

class="keyword">class CommandHistory {

class="keyword">private history: Command[] = [];

class="keyword">private current = -1;

execute(command: Command): class="keyword">void {

command.execute();

class="keyword">class="comment">// Remove commands after current position

class="keyword">this.history = class="keyword">this.history.slice(0, class="keyword">this.current + 1);

class="keyword">this.history.push(command);

class="keyword">this.current++;

}

undo(): class="keyword">void {

class="keyword">class="keyword">if (class="keyword">this.current >= 0) {

class="keyword">this.history[class="keyword">this.current].undo();

class="keyword">this.current--;

}

}

redo(): class="keyword">void {

class="keyword">class="keyword">if (class="keyword">this.current < class="keyword">this.history.length - 1) {

class="keyword">this.current++;

class="keyword">this.history[class="keyword">this.current].execute();

}

}

}

class="keyword">class="comment">// Usage

class="keyword">class="keyword">const editor = class="keyword">new TextEditor();

class="keyword">class="keyword">const history = class="keyword">new CommandHistory();

history.execute(class="keyword">new WriteCommand(editor, class="keyword">class="string">"Hello "));

console.log(editor.getContent()); class="keyword">class="comment">// class="keyword">class="string">"Hello "

history.execute(class="keyword">new WriteCommand(editor, class="keyword">class="string">"World!"));

console.log(editor.getContent()); class="keyword">class="comment">// class="keyword">class="string">"Hello World!"

history.undo();

console.log(editor.getContent()); class="keyword">class="comment">// class="keyword">class="string">"Hello "

history.redo();

console.log(editor.getContent()); class="keyword">class="comment">// class="keyword">class="string">"Hello World!"

React-Specific Patterns

Higher-Order Component (HOC)

app.ts

class="keyword">class="comment">// with-loading.tsx

class="keyword">import { ComponentType } class="keyword">from class="keyword">class="string">'react'

class="keyword">interface WithLoadingProps {

isLoading: boolean

}

class="keyword">class="keyword">function withLoading

class="keyword">extends object>(

Component: ComponentType

) {

class="keyword">class="keyword">return (props: P & WithLoadingProps) => {

class="keyword">class="keyword">const { isLoading, ...rest } = props

class="keyword">class="keyword">if (isLoading) {

class="keyword">class="keyword">return

Loading...

}

class="keyword">class="keyword">return as P)} />

}

}

class="keyword">class="comment">// Usage

class="keyword">interface UserListProps {

users: User[]

}

class="keyword">class="keyword">function UserList({ users }: UserListProps) {

class="keyword">class="keyword">return (

    {users.map((user) => (

  • {user.name}
  • ))}

)

}

class="keyword">class="keyword">const UserListWithLoading = withLoading(UserList)

class="keyword">class="comment">// In component

Render Props

app.ts

class="keyword">class="comment">// mouse-tracker.tsx

class="keyword">import { useState, ReactNode } class="keyword">from class="keyword">class="string">"react";

class="keyword">interface MousePosition {

x: number;

y: number;

}

class="keyword">interface MouseTrackerProps {

render: (position: MousePosition) => ReactNode;

}

class="keyword">class="keyword">function MouseTracker({ render }: MouseTrackerProps) {

class="keyword">class="keyword">const [position, setPosition] = useState({ x: 0, y: 0 });

class="keyword">class="keyword">const handleMouseMove = (e: React.MouseEvent) => {

setPosition({ x: e.clientX, y: e.clientY });

};

class="keyword">class="keyword">return (

class="keyword">class="string">"100vh" }}>

{render(position)}

);

}

class="keyword">class="comment">// Usage

render={({ x, y }) => (

Mouse position: ({x}, {y})

)}

/>;

Compound Components

app.ts

class="keyword">class="comment">// accordion.tsx

class="keyword">import { createContext, useContext, useState, ReactNode } class="keyword">from class="keyword">class="string">'react'

class="keyword">interface AccordionContextType {

activeIndex: number | class="keyword">null

setActiveIndex: (index: number) => class="keyword">void

}

class="keyword">class="keyword">const AccordionContext = createContextclass="keyword">undefined>(class="keyword">undefined)

class="keyword">class="keyword">function Accordion({ children }: { children: ReactNode }) {

class="keyword">class="keyword">const [activeIndex, setActiveIndex] = useStateclass="keyword">null>(class="keyword">null)

class="keyword">class="keyword">return (

class="keyword">class="string">"accordion">{children}

)

}

class="keyword">class="keyword">function AccordionItem({ index, title, children }: any) {

class="keyword">class="keyword">const context = useContext(AccordionContext)

class="keyword">class="keyword">if (!context) class="keyword">throw class="keyword">new Error(class="keyword">class="string">'AccordionItem must be used within Accordion')

class="keyword">class="keyword">const { activeIndex, setActiveIndex } = context

class="keyword">class="keyword">const isActive = activeIndex === index

class="keyword">class="keyword">return (

{isActive &&

{children}
}

)

}

Accordion.Item = AccordionItem

class="keyword">class="comment">// Usage

0} title=class="keyword">class="string">"Section 1">

Content 1

1} title=class="keyword">class="string">"Section 2">

Content 2

Anti-Patterns to Avoid

God Object - Class doing too many things

Premature Optimization - Optimizing before measuring

Golden Hammer - Using same pattern everywhere

Spaghetti Code - Unstructured, tangled code

Copy-Paste Programming - Duplicating code instead of abstracting

Best Practices

1. Understand the problem first - Don't force patterns

2. KISS Principle - Keep it simple, stupid

3. YAGNI - You aren't gonna need it

4. Favor composition over inheritance - More flexible

5. Program to interfaces, not implementations - Loose coupling

6. Open/Closed Principle - Open for extension, closed for modification

7. Use TypeScript - Type safety prevents errors

8. Write tests - Ensure patterns work correctly

9. Document patterns - Help team understand architecture

10. Review and refactor - Patterns evolve with requirements

Conclusion

Design patterns are tools, not rules. Use them when they solve a real problem and make code more maintainable. Start with simple solutions, and introduce patterns as complexity grows. The best pattern is the one that makes your code easier to understand and maintain.

Remember: patterns should clarify, not complicate! 🎨