Course Content
Object-Oriented Design Interview
14 sections · 29 lessons
The seven design patterns, and when not to use one
Patterns are where preparation most often goes wrong in both directions: candidates either cannot name the structure an interviewer is hinting at, or they bolt four patterns onto a problem that needed one. This lesson gives each of the seven that matter a single page — problem, structure, when to use it, cost — and then the counterweight: how to decide not to use one.
Across hundreds of published low-level design prompts, the same seven patterns cover nearly everything. Each is one page here: the problem, the structure, when to use it, and its cost.
1. Strategy — swap an algorithm
Problem: one operation has several interchangeable implementations, and which one is used should be a configuration decision. Structure: an interface with one method; one class per algorithm; the context holds a reference and delegates. Use it when: you have branches selecting between rules that change independently — pricing, scheduling, allocation, a bot's decision logic. Cost: one interface plus one class per rule. Negligible when there are two or more rules, pure overhead when there is one.
public interface PricingStrategy { Money computeFee(Ticket t); }Appears in: parking lot pricing (Section 4), elevator scheduling (Section 8), locker allocation (Section 12), player decisions (Sections 10 and 11).
2. State — behaviour that depends on a lifecycle
Problem: an object behaves differently depending on which phase of its life it is in, and the same method must do different things — or be refused — in each. Structure: a State interface with one method per event; one class per state; the context holds a current state and delegates; each state returns or sets the next. Use it when: you find yourself writing if (status == X) … else if (status == Y) … in more than one method. Cost: one class per state, and transitions become distributed across those classes rather than visible in one table — which is why you also write the transition table down.
Appears in: vending machine (Section 7), ATM (Section 13), elevator car (Section 8), locker (Section 12), blackjack round (Section 11).
3. Factory — centralise creation
Problem: picking which concrete class to instantiate is a decision that would otherwise be duplicated at every creation site. Structure: a static method or a small class that takes a discriminator and returns the interface type. Use it when: creation involves a choice, and that choice appears in more than one place. Cost: one small class. The real cost is using an abstract factory — a factory of factories — where a static method would do. That is the version that reads as over-engineering.
1public static Vehicle create(VehicleType type, String plate) {2 return switch (type) {3 case MOTORCYCLE -> new Motorcycle(plate);4 case CAR -> new Car(plate);5 case TRUCK -> new Truck(plate);6 };7}4. Singleton — exactly one instance
Problem: something must exist once per process — a hardware device, a global registry. Structure: a private constructor and a static accessor. Use it when: genuinely rarely. Cost: higher than it looks. A singleton is global mutable state: it makes tests share state, hides dependencies, and needs care to be thread-safe.
The better answer in an interview is nearly always: create one instance at startup and pass it in. Say that out loud — "I'd make this a single instance created at startup and injected, rather than a Singleton, so it stays testable" — and you have turned a weak pattern into a scoring sentence.
5. Observer — one change, many reactions
Problem: several components must react to an event, and the source should not know who they are. Structure: the subject keeps a list of listeners and calls them on change; listeners register and unregister. Use it when: the set of reactors grows over time and is not the source's business — low-stock alerts, an order status reaching three different screens. Cost: control flow becomes hard to follow, and errors in one listener can affect the others unless you catch per listener. Also, ordering between listeners is not guaranteed.
Appears in: grocery low-stock reordering (Section 9), restaurant kitchen displays (Section 14).
6. Composite — treat one and many the same
Problem: a tree of objects where clients should not care whether they hold a leaf or a container. Structure: a common interface; a leaf class; a container class that holds children of the same interface type and recurses. Use it when: the domain is genuinely a tree — files and directories, nested filters, grouped menu items. Cost: the leaf ends up with methods that make no sense for it (a File has no add), which you resolve by keeping the shared interface small.
Appears in: file search, twice — the file tree and the filter combination (Section 6).
7. Command — an action as an object
Problem: an action needs to be queued, logged, undone, or sent somewhere before it runs. Structure: a Command interface with execute() and optionally undo(); one class per action; an invoker holds a queue or a stack. Use it when: you need undo, a request queue, or an audit log of actions. Cost: one class per action, which is real overhead if you never queue or undo anything.
Appears in: restaurant order modifications (Section 14), undo in tic tac toe (Section 10).
When not to use a pattern
Over-engineering is now a more common failure in this round than under-engineering, because candidates study patterns before they study problems. The seven pages above need this counterweight.
A design made worse by three patterns
The prompt: a tic tac toe game (the real design is Section 10). A candidate who has studied patterns produces:
1// 1. Abstract factory for board creation2public interface BoardFactory { Board createBoard(); }3public class StandardBoardFactory implements BoardFactory { … }45// 2. Singleton game manager6public class GameManager {7 private static GameManager instance;8 public static GameManager getInstance() { … }9}1011// 3. Observer on every cell12public interface CellObserver { void onCellChanged(int row, int col, Piece p); }13public class Cell { private List<CellObserver> observers = new ArrayList<>(); … }1415// 4. Command for every move16public interface MoveCommand { void execute(); void undo(); }Four patterns, roughly nine extra classes, on a problem with a 3×3 array and two players. Ask the test question of each:
| Pattern used | What change does it make cheap? | Is that change likely? |
|---|---|---|
| Abstract factory for boards | A second kind of board | There is one kind. No |
| Singleton game manager | Global access to the game | The game is passed in one place. No |
| Observer per cell | Many components reacting to one cell | Nothing observes a cell. No |
| Command per move | Undo, replay, logging | Undo was in the requirements. Yes |
One of four survives. The other three added classes, indirection, and — in the singleton's case — a shared instance that makes two concurrent games impossible. The candidate has made the design worse while demonstrating knowledge, which is precisely the trap.
The test to apply, every time
Before adding any pattern, answer two questions out loud:
- What change does this make cheap? Name the change in concrete terms. "Adding a weekend pricing rule." "Adding a fourth locker size."
- Is that change actually likely, given the requirements we agreed? If the requirements say one pricing rule and no more are coming, the answer is no.
Both answers must be yes. If the first has no concrete answer, the pattern is decoration.
The rule of three
A practitioner heuristic worth knowing: do not abstract until you have three cases. One implementation needs no interface. Two might be a coincidence. Three is a pattern of variation worth building for.
There is one exception in an interview. If the requirements or the interviewer's questions have named a second and third variant — "hourly now, but we're adding weekend rates" — you have your three cases already and should abstract immediately. That is a requirement, not a guess.
The sentence that scores either way
You get the point for the reasoning, not the structure:
"I could put a factory in front of vehicle creation, but there are three vehicle types and they're created in one place, so a static method is enough. If creation moved into several places I'd extract a factory then."
That sentence demonstrates knowing the pattern, knowing its cost, and knowing the trigger to adopt it — three things — while writing zero extra classes.