A design pattern is a reusable, proven solution to a problem that keeps showing up in software design. It is not code you copy and paste. It is a blueprint you adapt to your situation, and a shared name your team can use to talk about the design. The 23 classic patterns in this section come from the Gang of Four (GoF) book, Design Patterns: Elements of Reusable Object-Oriented Software (1994).
In the interview: You won't have time to implement a pattern in full. Name the pattern that fits the scenario, say why in a sentence, and move on. Study the patterns to understand why they exist and what problem each one solves — there's no need to memorize the templates.
Right before an interview: Brush up on the Pattern Reference Cards and Choosing the Right Pattern, at the bottom of this list.
What a pattern is (and isn't)#
Architects don't reinvent stairs every time they need to connect two floors. They reuse a known solution and adapt it: spiral stairs, straight stairs, an elevator. Software has the same kind of repeating problems:
- "How do we make sure only one connection pool exists?"
- "How do we notify several parts of the program when something changes?"
- "How do we add features to an object without modifying its class?"
| A pattern is not | A pattern is |
|---|---|
| Code you copy | A template you adapt |
| A library or framework you import | A shared vocabulary between developers |
| A rule you must follow | Distilled experience with known trade-offs |
Why patterns help#
Without patterns, every developer solves the same problem differently:
# Developer A: hard-coded notifications
class NewsSystem:
def something_happened(self) -> None:
email_system.send()
sms_system.send()
push_notification.send()
# Adding a new channel means editing this method
# Developer B: a different, ad-hoc approach to the same problem
class EventManager:
def __init__(self):
self.subscribers: list[str] = []
def notify_all_subscribers(self) -> None:
for sub in self.subscribers:
... # inconsistent with Developer A's design
This causes three problems:
- Reinventing the wheel. Each home-grown solution has to rediscover the same edge cases (for example, a subscriber unsubscribing in the middle of a notification loop).
- Communication barriers. "I built a callback mechanism with a registry..." takes a paragraph. "I used the Observer pattern" takes one sentence.
- Unproven designs. Custom solutions haven't been exercised across thousands of codebases.
With patterns you get:
- Proven solutions whose trade-offs are documented.
- A common vocabulary. "Singleton for the config" replaces a long explanation about controlling instance creation and a shared accessor.
- Faster problem solving. Recognize the problem, pick the pattern, adapt it.
- Better structure. Code that new teammates can recognize and navigate.
The three families#
The Gang of Four (GoF) grouped their 23 patterns into three families, based on what kind of problem they solve.
Creational: how objects are created#
Creating an object looks trivial (Thing()), until you need exactly one instance, don't know the concrete type until runtime, need many construction steps, or want to copy an existing object instead of building a new one.
| Pattern | One line |
|---|---|
| Singleton | Only one instance ever exists |
| Factory Method | Let subclasses decide which object to create |
| Abstract Factory | Create families of related objects |
| Builder | Build complex objects step by step |
| Prototype | Create objects by cloning an existing one |
# Without a pattern: creation logic repeated wherever you need one
if kind == "email":
notif = EmailNotification(recipient, subject, body, smtp_server)
elif kind == "sms":
notif = SMSNotification(phone_number, message, gateway)
elif kind == "push":
notif = PushNotification(device_id, title, message, icon)
# With a factory: creation lives in one place
notif = NotificationFactory.create(kind, params)
Structural: how objects are composed#
These patterns are about making pieces fit together: connecting incompatible interfaces, layering features onto objects, hiding complex subsystems, and sharing resources.
| Pattern | One line |
|---|---|
| Adapter | Make incompatible interfaces work together |
| Decorator | Add behavior to an object without modifying it |
| Facade | Give a complex system a simple interface |
| Composite | Treat single objects and groups the same way |
| Flyweight | Share common state to save memory |
| Bridge | Separate an abstraction from its implementation |
| Proxy | A stand-in that controls access to another object |
# Without a pattern: one subclass per combination
class CoffeeWithMilk(Coffee): ...
class CoffeeWithSugar(Coffee): ...
class CoffeeWithMilkAndSugar(Coffee): ...
class CoffeeWithMilkAndSugarAndWhip(Coffee): ... # class explosion
# With Decorator: combine layers at runtime
coffee = BasicCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
coffee = WhipDecorator(coffee)
Behavioral: how objects communicate#
These patterns assign responsibilities and define how objects talk: notifying others about changes, changing behavior with state, swapping algorithms, passing requests along a chain.
| Pattern | One line |
|---|---|
| Strategy | Swap algorithms at runtime |
| Observer | Notify many objects when one changes |
| Command | Wrap a request in an object |
| State | Change behavior when internal state changes |
| Template Method | Fix an algorithm's skeleton, let subclasses fill in steps |
| Iterator | Walk a collection without exposing its internals |
| Chain of Responsibility | Pass a request along a chain until someone handles it |
# Without a pattern: every payment type in one method
class Checkout:
def process_payment(self, kind: str, amount: float) -> None:
if kind == "creditcard":
... # 50 lines of card processing
elif kind == "paypal":
... # 30 lines of PayPal processing
elif kind == "crypto":
... # 40 lines of crypto processing
# With Strategy: the algorithm is a pluggable object
class StrategyCheckout:
def __init__(self, strategy: "PaymentStrategy"):
self._strategy = strategy
def process_payment(self, amount: float) -> None:
self._strategy.pay(amount)
Structural vs behavioral: the confusing part#
Creational is easy to spot. Structural and behavioral get mixed up, because both often involve one object holding a reference to another.
| Structural | Behavioral | |
|---|---|---|
| Focus | How pieces are assembled | How pieces interact and share responsibility |
| Mental picture | A photograph (static architecture) | A movie (dynamic interaction) |
| Key question | "How is this built?" | "How do these talk / act?" |
| Examples | Adapter wraps, Decorator layers, Facade hides | Observer notifies, Strategy swaps, Chain passes along |
# STRUCTURAL: Facade hides several classes behind one simple method
class ComputerFacade:
def __init__(self, cpu: "CPU", memory: "Memory", hd: "HardDrive"):
self._cpu = cpu
self._memory = memory
self._hd = hd
def start(self) -> None:
self._cpu.freeze()
self._memory.load()
self._hd.read()
# BEHAVIORAL: Observer defines who gets told when state changes
subject.attach(observer1)
subject.attach(observer2)
subject.set_state("new") # both observers are notified
The same domain can call for either. Switching databases:
# Adapter (structural): make MySQL's API fit *our* interface
class MySQLAdapter(DatabaseInterface):
def __init__(self, mysql: "MySQLDatabase"):
self._mysql = mysql
def query(self) -> None:
self._mysql.execute_query() # translate the call
# Strategy (behavioral): choose *how* data is saved at runtime
class DataService:
def __init__(self, strategy: "DatabaseStrategy"):
self._strategy = strategy
def save(self, data: "Data") -> None:
self._strategy.save(data)
- Adapter: "I need this square peg to fit a round hole." That's structure.
- Strategy: "I need to switch between different ways of saving." That's behavior.
Patterns in low-level design interviews#
In LLD interviews (design a parking lot, a ticket-booking system, a ride-hailing app) patterns give you:
- Vocabulary. "I'll use Strategy for pricing" communicates your approach instantly.
- Structure. A proven class layout to start from.
- Credibility. It shows you think about change and extension, not just "it works".
Interviewers don't expect you to recite all 23. They expect you to recognize which problem calls for which pattern. A sensible learning order:
- Must know: Singleton, Factory, Strategy, Observer
- Good to know: Builder, Decorator, Adapter, Facade
- Bonus: Command, State, Template Method, Prototype, Composite, Flyweight, Bridge
Key takeaways#
- A pattern is a blueprint plus a name, not a library or copy-paste code.
- Always understand the problem a pattern solves before learning its solution.
- Creational = object creation, Structural = composition, Behavioral = communication.
- Structural is about how things are built; behavioral is about how they act at runtime.
- You need about 8 patterns well for interviews; recognizing when to use them matters more than memorizing all 23.