What are Design Patterns
What are Design Patterns
1. Intuition — the problem they solve
When engineers design systems independently, they keep re-discovering the same recurring shapes. "I need one shared config object across the app." "I need to swap the payment provider without touching checkout code." "I need to notify many subscribers when one thing changes." Left unnamed, each engineer reinvents a slightly different solution, and code reviews devolve into re-explaining the same structural idea from scratch.
Design patterns exist to give these recurring solutions names and vetted structures. They are the distilled, battle-tested answers to problems that appear again and again in object-oriented design — extracted from real systems by practitioners (most famously the 1994 "Gang of Four" book) rather than invented top-down. The payoff is twofold: you don't reinvent, and you gain a shared vocabulary. Saying "let's put a Strategy here" transmits an entire design in two words to anyone who knows the catalog.
2. Precise definition / how it works
A design pattern is a named, reusable solution to a recurring design problem within a given context. Crucially, a pattern is not a library, a finished class, or copy-paste code. It is a template — a description of the participating roles (classes/objects), their relationships, and responsibilities — that you adapt to your concrete situation. The same pattern looks different in every codebase because you supply the domain types.
The GoF catalog groups 23 classic patterns into three families by what problem they address:
- Creational — control how objects are made (Factory Method, Abstract Factory, Builder, Prototype, Singleton). They decouple client code from concrete construction.
- Structural — compose objects and classes into larger structures (Adapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight). They deal with how things fit together.
- Behavioral — assign responsibilities and manage communication between objects (Strategy, Observer, Command, Template Method, State, Iterator, Chain of Responsibility, Mediator, Visitor). They deal with how things interact and vary at runtime.
Almost every pattern is a specific application of two deeper principles: program to an interface, not an implementation, and favor composition over inheritance. If you internalize those two, most patterns feel like inevitable consequences rather than tricks to memorize.
3. Concrete example — Strategy
Consider a shipping-cost calculator that must support several algorithms (flat rate, weight-based, third-party API) and let us swap them at runtime. Hard-coding an if/else chain couples the caller to every algorithm and forces edits to a central method each time a new one appears. Strategy factors each algorithm behind a common interface so the context holds one interchangeable reference.
// The strategy interface — the stable abstraction
interface ShippingCost {
BigDecimal cost(Order order);
}
// Interchangeable concrete strategies
class FlatRate implements ShippingCost {
public BigDecimal cost(Order o) { return new BigDecimal("5.00"); }
}
class ByWeight implements ShippingCost {
public BigDecimal cost(Order o) {
return o.weightKg().multiply(new BigDecimal("1.20"));
}
}
// The context depends on the interface, not any concrete class
class Checkout {
private ShippingCost strategy; // injected / swappable
Checkout(ShippingCost strategy) { this.strategy = strategy; }
void setStrategy(ShippingCost s) { this.strategy = s; }
BigDecimal total(Order o) {
return o.subtotal().add(strategy.cost(o)); // delegates, no branching
}
}
// Usage: behavior chosen at runtime
Checkout c = new Checkout(new ByWeight());
c.setStrategy(new FlatRate()); // swap without touching Checkout's logic
When it applies: you have a family of interchangeable algorithms, want to pick one at runtime, or want to eliminate a growing conditional that selects behavior. Checkout never changes when you add a new ShippingCost — you just write a new class. That is the Open/Closed Principle made concrete.
4. When to use / when NOT — the judgment layer
Patterns are tools with a cost, not badges of quality. Reach for one only when the force it manages is actually present.
- Strategy vs. a plain conditional: use Strategy when algorithms are numerous, change independently, or must be swapped at runtime/injected for testing. For two stable branches that will never grow, an
if/elseis simpler and better — a Strategy adds indirection and class sprawl for no gain. - Strategy vs. Template Method: both vary behavior. Template Method uses inheritance (subclass overrides steps of a fixed algorithm) — compile-time, one axis of variation. Strategy uses composition — runtime-swappable, testable in isolation, no fragile base class. Prefer Strategy unless the shared skeleton is genuinely fixed and you want to enforce it.
- Factory vs.
new: a factory earns its place only when construction is complex, varies by config, or you must decouple from concrete types. If you just need one object, callingnewis the honest choice. - Singleton — the contested one: it solves "exactly one instance," but introduces global mutable state, hides dependencies, and wrecks testability. In modern code a dependency-injection container managing a single scoped instance achieves the same lifetime without the global-access downside. It is defensible only for a genuine process-wide resource with a carefully managed lifecycle (a metrics registry, a connection pool) — and even then, expose it behind an injected interface so tests can substitute it. Interviewers often expect you to critique Singleton, not praise it.
The meta-rule: patterns manage change and variation. If a dimension isn't going to vary, abstracting it is speculative complexity (YAGNI). Apply patterns reactively — when a real force appears — far more often than proactively.
5. Pitfalls / what interviewers probe
- Pattern-itis (over-engineering): the classic tell of a mid-level engineer is forcing patterns everywhere — a
FactoryFactory, a Strategy for a single algorithm. Seniors demonstrate restraint and justify each abstraction by a concrete pressure. - "Explain the trade-offs," not "recite the definition": interviewers rarely want the textbook diagram. They ask "why this pattern over X?" and "what does it cost?" Knowing when not to use one signals seniority.
- Confusing similar patterns: Strategy vs. State (State transitions between its own concrete types; Strategy is externally chosen and static per call), Adapter vs. Facade vs. Proxy, Factory Method vs. Abstract Factory. Be ready to distinguish by intent, since their class diagrams often look alike.
- Ignoring the underlying principle: if you can reduce a pattern to "program to an interface" or "favor composition," you show you understand why it works, not just its shape.
- Patterns as a smell: sometimes a needed pattern reveals a language gap — first-class functions turn Strategy/Command into a mere lambda. Recognizing that patterns are partly a workaround for language limits is a strong senior signal.
Key takeaways
- A design pattern is a named, adaptable template for a recurring OO design problem — a shared vocabulary and vetted structure, not a library or copy-paste code.
- The GoF catalog splits into Creational (object creation), Structural (composition), and Behavioral (interaction) families.
- Most patterns are applications of two principles: program to an interface and favor composition over inheritance.
- Patterns manage variation and change; if a dimension won't vary, applying one is speculative complexity — prefer the simpler construct.
- Seniority shows in restraint and trade-off reasoning (Strategy vs. conditional, Strategy vs. Template Method, Singleton vs. DI), not in naming or over-applying patterns.
L0 · a design pattern is a named, adaptable template for a recurring OO problem — value comes only from the force it manages, not from its name.
L1 · ⑥ Cost/Simplicity — "make this extensible for future payment providers"
Trap: add a PaymentStrategy interface and a class per provider now, "so it's future-proof," even though exactly one provider exists today.
Bar: the deciding variable is N, the count of concrete variants that exist today plus a committed (not hoped-for) second one — at N=1 the interface, extra file, and extra test double are pure carrying cost with zero flexibility realized yet; ship the concrete class and extract the interface when N=2 actually lands (rule of three), because YAGNI abstraction is a liability, not an asset, until it's paying rent. connects-to: coupling vs. cohesion
L2 · ⑤ Adversary/Edge — "isn't this just Strategy?" asked of a state machine
Trap: "same shape, an interface plus swappable implementations — pick either, they're interchangeable."
Bar: the deciding variable is who owns the transition — State requires the object to switch its own internal reference as invariants change (self-driven, sequential), while Strategy is client-selected and static for the duration of one call; if you let a "Strategy" trigger the next strategy internally you've smuggled State's self-transition responsibility into a shape that was never designed to protect that invariant, so an adversarial "can it trigger the next one?" question exposes a broken encapsulation boundary, not a naming quibble. connects-to: State pattern
L3 · ① Concurrency — is your Singleton thread-safe?
Trap: "it's a Singleton, so by definition there's only one instance — that's what makes it safe."
Bar: the deciding variable is whether the lazy-init reference is volatile under double-checked locking — without it, the Java Memory Model permits the constructor's field writes to be reordered past the reference publish, so a second thread can see a non-null reference to a partially-constructed object; the fix is volatile DCL, the static holder idiom, or an enum singleton, and "only one instance" says nothing about visibility across threads. connects-to: Singleton thread-safe variants
L4 · ② Failure — a retry-Proxy wraps a caching-Decorator wraps the real service
Trap: "the layers are independent, just add a retry decorator on top and failures are handled."
Bar: the deciding variable is composition order, because it fixes which layer owns which failure domain — retry outside cache re-hits the real backend on a transient error, but retry inside cache can retry against (or poison) a cache that already absorbed a bad response; you must reason explicitly about which wrapped layer sees the failure first, not just stack more decorators and assume resilience composes for free. connects-to: Decorator pattern
L5 · ④ Time/Lifecycle — a two-year-old Visitor now needs a new type and a new operation
Trap: "Visitor is extensible — just add another visit method to the hierarchy."
Bar: the deciding variable is which axis of change the pattern optimized for at adoption time — Visitor trades cheap-new-operations for expensive-new-types (every visitor implementation must add a method), so once the change axis flips and types start outgrowing operations, the "correct" choice from two years ago has ossified into the costliest one to extend; the fix is re-deriving the pattern from the current change axis (often back to plain polymorphism), which is a migration, not a patch. connects-to: Visitor pattern
The floor keeps dropping: Singleton config + an Observer event bus + a Decorator middleware chain are composed in prod; a network partition splits the observer subscribers mid-broadcast while the Singleton's lazily-initialized state is being read by threads on both sides of the split. Diagnose the emergent failure, and given a two-hour deadline, name which pattern choice you'd undo first and why — "add more patterns" is never the answer under a clock.
Self-locate: died at L1 → mid-level; L4+ → staff signal.
Facing any new concept? Hit it with the six: concurrent? failing? at 100×? over time? adversarial? worth the cost? — that's the interviewer's whole playbook.
🤖 Don't fully get this? Learn it with Claude
Stuck on What are Design Patterns? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **What are Design Patterns** (OO & Low-Level Design) and want to truly understand it. Explain What are Design Patterns from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **What are Design Patterns** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **What are Design Patterns** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **What are Design Patterns** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.