What you'll learn
Quick Answer
A pattern is a named, reusable solution to a recurring design problem. The five worth knowing first are Singleton, Factory, Strategy, Observer and Decorator. Recognise them in code you read; do not go looking for places to insert them.
What they are, and the warning first
Patterns were catalogued because experienced developers kept arriving at the same shapes independently. Naming them made design discussions shorter — "use a strategy here" replaces five minutes of description.
The warning matters as much as the patterns. Someone who has just learned them tends to apply them everywhere, and the result is three interfaces and a factory wrapping what should have been one function. That is worse than the code it replaced, because indirection makes reading harder.
Patterns are a response to a problem you already have. If the problem has not appeared, applying the pattern adds cost with no benefit. "Which pattern should I use here?" is usually the wrong question; "what is hard to change in this code?" is the right one.
Singleton — one instance, globally
Ensures a class has exactly one instance with a global access point. Used for database connection pools, configuration and loggers.
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.settings = {}
return cls._instance
a = Config()
b = Config()
print(a is b) # True -- the same object
It is also the most criticised pattern, for good reasons. It is global mutable state under another name, it makes testing awkward because tests share the instance, and it hides dependencies — a class using a singleton does not declare it needs one.
In Python a module is already a singleton, so a module-level object usually achieves this with none of the ceremony. Know the pattern for interviews; reach for dependency injection instead in real code.
Factory and Strategy
Factory moves object creation behind a function, so callers do not name concrete classes:
def get_parser(filename):
if filename.endswith(".csv"): return CsvParser()
if filename.endswith(".json"): return JsonParser()
raise ValueError(f"unsupported file: {filename}")
parser = get_parser(name)
data = parser.parse(name)
Adding an XML parser means changing one function, not every call site. The problem it solves is a growing if chain about types scattered through the codebase.
Strategy makes an algorithm swappable at runtime:
def apply_discount(cart, strategy):
return strategy(cart.total)
flat_50 = lambda total: total - 50
percent_10 = lambda total: total * 0.9
apply_discount(cart, percent_10)
The classic sign you need it is a function containing a large if/elif selecting between behaviours that each vary independently. In languages with first-class functions, Strategy is often just passing a function — no classes required, which is a good example of a pattern being simpler than its textbook form.
Observer and Decorator
Observer lets objects subscribe to events instead of being called directly:
class OrderService:
def __init__(self):
self.listeners = []
def subscribe(self, fn):
self.listeners.append(fn)
def place_order(self, order):
save(order)
for fn in self.listeners:
fn(order)
service.subscribe(send_email)
service.subscribe(update_inventory)
Without it, place_order calls email, inventory and analytics directly and must change every time a new consequence is added. With it, the order service knows nothing about them. Every event listener you have used in JavaScript is this pattern.
Decorator wraps behaviour around something without modifying it. Python has syntax for it:
def log_calls(fn):
def wrapper(*args, **kwargs):
print(f"calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper
@log_calls
def process(order): ...
Logging, caching, timing and authentication checks are all naturally decorators, because they are concerns that apply to many functions without belonging to any of them.
How to actually use this knowledge
Read code and name what you see. Express middleware is Chain of Responsibility. React hooks are closures over state. @app.route in Flask is a decorator. Recognising these makes unfamiliar codebases far easier to navigate, which is the main practical payoff.
In interviews you may be asked to name patterns or spot one in a snippet. A strong answer names the pattern and the problem it solves — "Strategy, because the discount rules vary independently and we do not want a growing conditional".
When writing code, let patterns emerge. Write the straightforward version first. When it becomes hard to change — the same conditional appearing in three files, a class needing edits for every new type — that is when the pattern has earned its place. See SOLID principles for the ideas underneath most of them.
