Introduction to Design Patterns
Concept
In 1994, four software engineers known as the “Gang of Four” (GoF) published a legendary book titled Design Patterns: Elements of Reusable Object-Oriented Software.
They realized that across thousands of different software applications, developers were constantly reinventing the exact same structural solutions to the exact same architectural problems.
A Design Pattern is a formalized, universally recognized blueprint for solving a common software design problem.
Design Patterns are NOT algorithms (like QuickSort or Binary Search). An algorithm provides a strict mathematical recipe to achieve a specific computational result. A Design Pattern is a high-level template for how to structure your classes and objects to make your codebase flexible, scalable, and maintainable.
The Three Categories
The 23 original Gang of Four patterns are divided into three distinct categories based on their primary purpose.
1. Creational Patterns
These patterns deal with Object Creation mechanisms.
Instead of blindly instantiating objects using the new keyword scattered throughout your codebase, Creational patterns hide the instantiation logic. This gives the program the flexibility to decide which objects need to be created for a given use case.
Examples: Singleton, Factory Method, Abstract Factory, Builder.
2. Structural Patterns
These patterns deal with Object Composition.
They define how classes and objects are mathematically assembled into larger, more complex structures, while keeping these structures flexible and efficient.
Examples: Decorator, Adapter, Facade, Proxy.
3. Behavioral Patterns
These patterns deal with Communication between Objects.
They define how objects talk to each other, how they distribute responsibilities, and how they manage complex control flows without becoming tightly coupled.
Examples: Observer, Strategy, Command, Iterator.
Why use Design Patterns?
- A Universal Vocabulary: If you tell a senior engineer: “I used a Factory to generate the database connections and wrapped them in a Singleton,” they instantly understand the exact architecture of your codebase without looking at a single line of code.
- Preventing Technical Debt: Patterns inherently force you to follow SOLID principles (like the Single Responsibility Principle and Open/Closed Principle), preventing “Spaghetti Code” where every class is tightly coupled to every other class.
- Reusability: Patterns are proven, battle-tested solutions to problems that have existed for 30 years. You don’t need to reinvent the wheel.
Interview Strategy
You will rarely be asked to “Write a Decorator Pattern from scratch” on a whiteboard (unless you are interviewing for a highly specific Senior Architecture role).
However, System Design Interviews are heavily reliant on Design Patterns.
When the interviewer asks: “How does the Notification Service know when the User Service creates a new account?”
You must instantly answer: “The User Service implements the Observer Pattern. The Notification Service subscribes to it as a Listener.”
Understanding when to use a pattern is significantly more important than memorizing the exact TypeScript syntax for it.
Interview Questions
Q: Are Design Patterns relevant in functional languages like React or Haskell?
A: Yes and No. The Gang of Four patterns were specifically designed for Object-Oriented Programming (Java, C++, C#). In purely functional paradigms, many of these patterns are unnecessary because higher-order functions and closures naturally solve the problems. However, modern frameworks still heavily rely on the concepts. For example, React’s useEffect dependencies and Redux’s Publish/Subscribe architecture are literally just modern implementations of the Observer Pattern.
Q: Is it possible to overuse Design Patterns?
A: Yes. This is called “Patternitis” or “Over-engineering”. If you are building a simple “To-Do List” app, and you create an AbstractTaskFactory that uses a TaskBuilder injected into a SingletonTaskRepository, you have destroyed your codebase. Patterns introduce abstraction overhead. You should only introduce a pattern when the codebase physically begins to suffer from the specific problem that the pattern was designed to solve.