Interviewers rarely ask you to recite the definition of the Decorator pattern. They hand you a class that’s sprouting flags and conditionals and ask how you’d clean it up, or they describe three payment providers with mismatched APIs behind one checkout button and wait to see what you name. Knowing the Gang of Four catalog matters less for the trivia and more because you can look at a tangled design and recognize the shape before you write a line.
The original 1994 book splits 23 patterns into three groups: creational (how objects get made), structural (how they’re composed), and behavioral (how they talk to each other). Most working engineers use maybe eight of them by name and a few more without noticing. The full catalog, what each one does, and where you’ve probably already run into it:
| Pattern | Group | What it does and when to reach for it | Where you’ve already seen it |
|---|---|---|---|
| Factory Method | Creational | Lets a subclass decide which concrete class to instantiate; reach for it when a class can’t know the exact type it must create. | Calendar.getInstance(), JDBC DriverManager |
| Abstract Factory | Creational | Produces whole families of related objects behind one interface; reach for it to swap an entire product set at once. | Cross-platform UI toolkits, DocumentBuilderFactory |
| Builder | Creational | Assembles a complex object step by step; reach for it to escape constructors with a pile of optional parameters. | StringBuilder, HttpRequest.newBuilder(), Lombok @Builder |
| Prototype | Creational | Copies an existing object instead of building a new one; reach for it when construction is expensive or config-heavy. | Object.clone(), JavaScript Object.create() |
| Singleton | Creational | Guarantees a single instance with a global access point; reach for it rarely, and only for a genuinely single resource. | Runtime.getRuntime(), most logger handles |
| Adapter | Structural | Converts one interface into another so incompatible classes cooperate; reach for it to fit a third-party API to your code. | InputStreamReader, Arrays.asList() |
| Bridge | Structural | Separates an abstraction from its implementation so each varies on its own; reach for it to avoid a combinatorial class explosion. | JDBC drivers, device-independent graphics layers |
| Composite | Structural | Treats a single object and a group of them through one interface; reach for it for tree structures. | The DOM, file systems, React element trees |
| Decorator | Structural | Wraps an object to add behavior while keeping its interface identical; reach for it to avoid subclass explosion for optional features. | java.io streams, Python function decorators |
| Facade | Structural | Puts a small, simple interface in front of a complicated subsystem; reach for it to hide internal wiring. | SLF4J, most SDK client classes |
| Flyweight | Structural | Shares common immutable state across many objects to cut memory; reach for it when you hold huge numbers of similar objects. | Integer cache (-128 to 127), String interning |
| Proxy | Structural | Substitutes a stand-in that controls access to the real object; reach for it for lazy loading, caching, or access checks. | Hibernate lazy entities, Spring AOP proxies |
| Chain of Responsibility | Behavioral | Passes a request along handlers until one deals with it; reach for it for filter and middleware pipelines. | Servlet filters, Express middleware |
| Command | Behavioral | Packages an action as an object you can queue, log, or undo; reach for it for undo/redo and task queues. | Runnable, GUI menu actions, job queues |
| Interpreter | Behavioral | Evaluates sentences of a small grammar; reach for it for simple DSLs and expression rules. | Regex engines, Spring Expression Language |
| Iterator | Behavioral | Walks a collection without exposing how it stores elements; reach for it to give uniform traversal. | Java Iterator, Python __iter__ |
| Mediator | Behavioral | Routes interaction through one hub instead of object-to-object; reach for it to cut many-to-many coupling. | Chat-room hubs, UI dialog coordinators |
| Memento | Behavioral | Captures and later restores an object’s state; reach for it for snapshots and undo history. | Text-editor undo, database savepoints |
| Observer | Behavioral | Notifies a list of subscribers when a subject changes; reach for it for event and reactive systems. | DOM event listeners, RxJS, React state |
| State | Behavioral | Alters an object’s behavior as its internal state changes; reach for it to replace sprawling status conditionals. | TCP connection states, order lifecycles |
| Strategy | Behavioral | Makes algorithms interchangeable behind one interface; reach for it to swap logic without touching callers. | Comparator, pluggable payment or compression |
| Template Method | Behavioral | Fixes an algorithm’s skeleton and lets subclasses fill in steps; reach for it when the order is fixed but the steps vary. | AbstractList, framework lifecycle hooks |
| Visitor | Behavioral | Adds new operations to a type hierarchy without editing the types; reach for it when operations change more often than the types. | AST traversal in compilers, file-tree processors |
The handful you’ll actually get asked about
Strategy is the one to know cold. It replaces a pile of conditionals with interchangeable objects, or plain functions, behind a single interface. If you’ve written a Comparator in Java or passed a key function to sorted() in Python, you’ve used it. The interview version usually sounds like “we have flat-rate, weight-based, and zone-based shipping, with more rules coming, how do you keep the order service from turning into a giant if/else.” The answer is a ShippingStrategy interface with one implementation per rule, picked at runtime.
class Checkout:
def __init__(self, pay):
self.pay = pay # any callable(amount) -> receipt
def complete(self, amount):
return self.pay(amount)
# swap the algorithm without touching Checkout
Checkout(stripe_charge).complete(4200)
Checkout(paypal_charge).complete(4200)
Factory Method and Abstract Factory come up as a pair, and the distinction is the whole question. Factory Method hides which concrete class you instantiate behind a method a subclass can override. Abstract Factory sits a level higher: it produces families of related objects, so you swap a whole set at once. Calendar.getInstance() returning a locale-specific calendar is the textbook Factory Method. A UI toolkit that hands you matching buttons, scrollbars, and menus for the current platform is Abstract Factory.
Observer sits behind every event system you’ve touched. One subject keeps a list of subscribers and notifies them on change. DOM addEventListener, RxJS streams, and React’s state subscriptions are all this pattern. Interviewers like it because the naive version has real bugs worth talking through: what happens when a subscriber throws mid-notification, how you avoid calling a listener that unsubscribed during the callback, and whether notification order is guaranteed at all.
Decorator and Adapter get confused constantly, so be ready to pull them apart. Adapter changes an interface so two incompatible things work together, like wrapping a legacy logger to satisfy your new logging interface. Decorator keeps the interface identical and adds behavior by wrapping, which is how java.io stacks a BufferedReader around a FileReader around an InputStreamReader. Same shape on the outside, extra work on the inside.
Builder turns up whenever a constructor has grown too many parameters, half of them optional. Instead of five overloaded constructors, you chain calls and build once. StringBuilder, HttpRequest.newBuilder(), and Lombok’s @Builder are the ones to cite when asked for an example.
Strategy versus State, the one that trips people up
These two have nearly identical class diagrams, and the difference is intent rather than structure. Strategy swaps an algorithm the caller chooses, and the strategies never know about each other. State changes an object’s behavior as its internal condition changes, and the states usually decide which state comes next. A checkout that lets a user pick Stripe or PayPal is Strategy. An order that moves from pending to paid to shipped, each stage allowing different actions, is State. If the objects hand control to one another, you’re looking at State. If something outside picks one and moves on, it’s Strategy.
Why Singleton keeps getting called an anti-pattern
Singleton is the pattern most likely to earn you a follow-up, and rarely a friendly one. It guarantees one instance with a global access point, which sounds tidy until you try to test the code around it. A singleton is global state wearing a class, so any test that touches it inherits whatever the previous test left behind, and you can’t slot in a fake. The senior answer is that you usually want one instance without the Singleton pattern: build the object once at startup and pass it in through dependency injection, so the “one instance” decision lives in your wiring instead of being baked into the class. Plenty of places are fine, like a stateless logger or a runtime handle, but reach for it knowing the interviewer may push.
Questions phrased the way they’re actually asked
- “This class has a switch statement that grows every time we add a feature. What would you use instead?” (Strategy, or State if the cases represent a lifecycle.)
- “What’s the difference between Factory Method and Abstract Factory?”
- “How would you unit-test a class that depends on a Singleton?”
- “Where have you seen the Decorator pattern in a standard library?”
- “We need logging, retries, and caching around the same service call. Wrap it or subclass it?” (Decorator or Proxy, not a subclass per concern.)
The trap in these rounds is the mirror image of not knowing patterns: naming one for every problem. A well-placed Strategy interface earns its keep, while a Visitor bolted onto a three-class hierarchy just makes the reviewer wonder what you were defending against. The answers that land name the pattern, say why it fits, and then admit when a plain function or a small class would do the same job with less ceremony.
Drill the patterns next:
