DEV Community

Cover image for Understanding 23 Design Patterns in Software Development
Kishan vyas
Kishan vyas

Posted on

Understanding 23 Design Patterns in Software Development

If you're diving deeper into software development and want to write cleaner, more scalable code — design patterns are your new best friends.

In this guide, I’ll walk you through the 23 most commonly used design patterns, grouped under Creational, Structural, and Behavioral patterns — all explained simply and with real-world examples in JavaScript.

Let’s go.........


📦 Creational Design Patterns

These deal with object creation mechanisms. The idea is to abstract the instantiation logic so that the system is not tightly coupled to the specific classes.


1. Singleton Pattern

Only one instance of a class is created and shared.

Use case: Logger, Config manager, DB connection.

class Singleton {
  constructor() {
    if (Singleton.instance) return Singleton.instance;
    Singleton.instance = this;
  }
}

const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true
Enter fullscreen mode Exit fullscreen mode

Image description


2. Factory Pattern

Creates objects without exposing the instantiation logic.

Use case: UI component factory, Notification sender (Email/SMS).

class Car {
  drive() {
    console.log("Driving a car");
  }
}

class Bike {
  drive() {
    console.log("Riding a bike");
  }
}

function VehicleFactory(type) {
  if (type === "car") return new Car();
  if (type === "bike") return new Bike();
}

const vehicle = VehicleFactory("bike");
vehicle.drive(); // Riding a bike
Enter fullscreen mode Exit fullscreen mode

3. Abstract Factory Pattern

Factory of factories.

Use case: Theme factory for UI (DarkThemeFactory, LightThemeFactory)

function darkButton() {
  return { color: "black" };
}

function lightButton() {
  return { color: "white" };
}

function UIFactory(mode) {
  return {
    createButton: mode === "dark" ? darkButton : lightButton,
  };
}

const factory = UIFactory("dark");
console.log(factory.createButton()); // { color: "black" }
Enter fullscreen mode Exit fullscreen mode

4. Builder Pattern

Step-by-step creation of a complex object.

Use case: Building complex HTML, query builder, or form builders.

class BurgerBuilder {
  constructor() {
    this.burger = {};
  }

  addCheese() {
    this.burger.cheese = true;
    return this;
  }

  addLettuce() {
    this.burger.lettuce = true;
    return this;
  }

  build() {
    return this.burger;
  }
}

const burger = new BurgerBuilder().addCheese().addLettuce().build();
console.log(burger); // { cheese: true, lettuce: true }
Enter fullscreen mode Exit fullscreen mode

5. Prototype Pattern

Clone an existing object instead of creating a new one.

Use case: Performance optimization, default object templates.

const car = {
  wheels: 4,
  drive() {
    console.log("Driving...");
  },
};

const myCar = Object.create(car);
console.log(myCar.wheels); // 4
Enter fullscreen mode Exit fullscreen mode

Image description

🧱 Structural Design Patterns

These are about class and object composition, focusing on how large structures are built from smaller pieces.


6. Adapter Pattern

Converts one interface to another.

Use case: Legacy APIs, payment gateway adapters.

class OldPrinter {
  oldPrint(msg) {
    console.log("Old Print:", msg);
  }
}

class Adapter {
  constructor() {
    this.oldPrinter = new OldPrinter();
  }

  print(msg) {
    this.oldPrinter.oldPrint(msg);
  }
}

const printer = new Adapter();
printer.print("Hello World");
Enter fullscreen mode Exit fullscreen mode

7. Decorator Pattern

Add new functionality without modifying the original class.

Use case: Middleware, logging wrappers.

function coffee() {
  return "coffee";
}

function withMilk(coffeeFunc) {
  return () => coffeeFunc() + " + milk";
}

const fancyCoffee = withMilk(coffee);
console.log(fancyCoffee()); // coffee + milk
Enter fullscreen mode Exit fullscreen mode

8. Facade Pattern

Simplifies a complex subsystem by exposing a high-level API.

Use case: Simplify media player libraries, SDKs.

class Engine {
  start() {
    console.log("Engine started");
  }
}

class Lights {
  on() {
    console.log("Lights on");
  }
}

class CarFacade {
  constructor() {
    this.engine = new Engine();
    this.lights = new Lights();
  }

  startCar() {
    this.engine.start();
    this.lights.on();
  }
}

const car = new CarFacade();
car.startCar();
Enter fullscreen mode Exit fullscreen mode

9. Composite Pattern

Treat a group of objects the same way as a single object.

Use case: File system, nested UI elements.

class Leaf {
  constructor(name) {
    this.name = name;
  }

  show() {
    console.log(this.name);
  }
}

class Folder {
  constructor(name) {
    this.name = name;
    this.children = [];
  }

  add(child) {
    this.children.push(child);
  }

  show() {
    console.log(this.name);
    this.children.forEach(child => child.show());
  }
}

const file1 = new Leaf("file1.txt");
const file2 = new Leaf("file2.txt");

const folder = new Folder("MyFolder");
folder.add(file1);
folder.add(file2);

folder.show();
Enter fullscreen mode Exit fullscreen mode

10. Bridge Pattern

Separate abstraction from implementation.

Use case: Theme-switching, rendering strategies.

class LightTheme {
  getColor() {
    return "Light color";
  }
}

class DarkTheme {
  getColor() {
    return "Dark color";
  }
}

class Page {
  constructor(theme) {
    this.theme = theme;
  }

  getContent() {
    return `Page with ${this.theme.getColor()}`;
  }
}

const darkPage = new Page(new DarkTheme());
console.log(darkPage.getContent()); // Page with Dark color
Enter fullscreen mode Exit fullscreen mode

11. Proxy Pattern

A placeholder that controls access to another object.

Use case: Lazy loading, access control, logging.

class RealImage {
  display() {
    console.log("Displaying image");
  }
}

class ProxyImage {
  constructor() {
    this.realImage = null;
  }

  display() {
    if (!this.realImage) {
      this.realImage = new RealImage();
    }
    this.realImage.display();
  }
}

const img = new ProxyImage();
img.display();
Enter fullscreen mode Exit fullscreen mode

🧠 Behavioral Design Patterns

These are all about how objects interact with each other.


12. Observer Pattern

Notify all subscribers when something changes.

Use case: Event listeners, Pub/Sub.

class Subject {
  constructor() {
    this.subscribers = [];
  }

  subscribe(fn) {
    this.subscribers.push(fn);
  }

  notify(data) {
    this.subscribers.forEach(fn => fn(data));
  }
}

const subject = new Subject();
subject.subscribe(console.log);
subject.notify("Hello!");
Enter fullscreen mode Exit fullscreen mode

13. Strategy Pattern

Change the algorithm at runtime.

Use case: Sorting strategies, payment gateways.

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

function calculate(strategy, a, b) {
  return strategy(a, b);
}

console.log(calculate(add, 5, 3)); // 8
console.log(calculate(multiply, 5, 3)); // 15
Enter fullscreen mode Exit fullscreen mode

14. Command Pattern

Encapsulate requests as objects.

Use case: Undo/Redo, menu actions.

class Light {
  turnOn() {
    console.log("Light on");
  }
}

class Command {
  constructor(device) {
    this.device = device;
  }

  execute() {
    this.device.turnOn();
  }
}

const light = new Light();
const cmd = new Command(light);
cmd.execute();
Enter fullscreen mode Exit fullscreen mode

15. Chain of Responsibility Pattern

Pass requests down a chain until someone handles it.

Use case: Express middlewares, support ticket routing.

function handler1(req, next) {
  if (req.type === "log") console.log("Handler1 processed");
  else next();
}

function handler2(req) {
  console.log("Handler2 processed");
}

function processRequest(req) {
  handler1(req, () => handler2(req));
}

processRequest({ type: "data" }); // Handler2 processed
Enter fullscreen mode Exit fullscreen mode

16. Template Method Pattern

Define the skeleton of an algorithm, but let subclasses fill in the steps.

Use case: Framework hooks.

class Game {
  start() {
    this.init();
    this.play();
    this.end();
  }

  init() {}
  play() {}
  end() {}
}

class Football extends Game {
  init() {
    console.log("Football Initialized");
  }

  play() {
    console.log("Playing Football");
  }

  end() {
    console.log("Football Ended");
  }
}

new Football().start();
Enter fullscreen mode Exit fullscreen mode

17. State Pattern

Change behavior based on internal state.

Use case: Media players, form steps, UI modes.

class TrafficLight {
  constructor() {
    this.states = ["green", "yellow", "red"];
    this.current = 0;
  }

  change() {
    console.log("Light:", this.states[this.current]);
    this.current = (this.current + 1) % this.states.length;
  }
}

const light = new TrafficLight();
light.change(); // green
light.change(); // yellow
Enter fullscreen mode Exit fullscreen mode

18. Mediator Pattern

Centralized communication between objects.

Use case: Chatrooms, component interactions.

class ChatRoom {
  showMessage(user, message) {
    console.log(`${user}: ${message}`);
  }
}

const room = new ChatRoom();
room.showMessage("Alice", "Hello Bob!");
Enter fullscreen mode Exit fullscreen mode

19. Visitor Pattern

Add operations to objects without changing their classes.

Use case: Compilers, object structure traversal.

class Book {
  accept(visitor) {
    visitor.visitBook(this);
  }
}

class BookVisitor {
  visitBook(book) {
    console.log("Visiting book");
  }
}

const book = new Book();
book.accept(new BookVisitor());
Enter fullscreen mode Exit fullscreen mode

20. Interpreter Pattern

Build a simple language interpreter.

Use case: Regex, Expression evaluators.

class Number {
  constructor(value) {
    this.value = value;
  }

  interpret() {
    return this.value;
  }
}

const one = new Number(1);
const two = new Number(2);
console.log(one.interpret() + two.interpret()); // 3
Enter fullscreen mode Exit fullscreen mode

21. Memento Pattern

Capture and restore an object's state.

Use case: Undo features, save/restore.

class Editor {
  constructor() {
    this.content = "";
  }

  write(text) {
    this.content += text;
  }

  save() {
    return this.content;
  }

  restore(saved) {
    this.content = saved;
  }
}
Enter fullscreen mode Exit fullscreen mode

22. Iterator Pattern

Access elements without exposing the structure.

Use case: Custom collections.

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 1, done: false }
Enter fullscreen mode Exit fullscreen mode

23. Null Object Pattern

Use an object that does nothing instead of null.

Use case: Avoid if (obj) checks.

class NullUser {
  getName() {
    return "Guest";
  }
}

function getUser(user) {
  return user || new NullUser();
}

console.log(getUser(null).getName()); // Guest
Enter fullscreen mode Exit fullscreen mode

Happy Coding 👨‍💻🔥

Image description

Top comments (0)