If you remember five things
The whole pattern in one glance. Everything below defends these five calls.
- Intent. Swap one of several interchangeable algorithms at runtime, behind a shared interface, without reopening the caller.
- Three roles. Strategy (the interface), Concrete Strategy (each implementation), Context (holds one strategy and delegates to it).
- Injection is the trick. The Context receives its strategy from outside (constructor or setter), it never picks its own. That is what makes it swappable and testable.
- Open-Closed in practice. A new variant is a new class, not an edit to code that already works and is tested.
- The classic gotcha. Moving the giant switch inside the Context defeats the whole pattern. You renamed the conditional, you did not delete it.
1The problem it solves#
Start with the pain, because that is what the pattern is reacting to. Picture a checkout service that calculates shipping cost. The first version is small and fine:
double shippingCost(Order order, String method) {
if (method.equals("STANDARD")) {
return order.weight() * 0.5;
} else if (method.equals("EXPRESS")) {
return order.weight() * 0.5 + 10;
} else if (method.equals("OVERNIGHT")) {
return order.weight() * 0.5 + 25;
}
throw new IllegalArgumentException("unknown method");
}
Then it grows, the way these always do.
- A product manager asks for a fourth option. Then a fifth.
- One new option calls an external carrier API. One depends on the day of the week.
- Six months later the method is 180 lines and every branch has its own bug history.
- Nobody wants to touch it, because changing one path risks breaking the other four.
The real problem is not the if-else, it is the ownership.
- One class now knows how to do every version of the job.
- It changes every time any single version changes.
- That breaks the Open-Closed Principle (the O in SOLID): open for extension, closed for modification.
- You keep editing a class that was already working and tested, instead of adding new code beside it.
Strategy fixes this by pulling each variant into its own class behind a shared interface.
Define a family of algorithms, put each one in its own class, and make them interchangeable behind a common interface. The caller chooses which one to use and never learns how any of them work.
2The idea (mental model)#
The mental model: one task, many interchangeable ways to do it.
- Define the shape of the task as an interface.
- Each way of doing it becomes its own small class.
- The object that needs the task done holds a reference to one of those classes and just calls it, never knowing which concrete one it got.
A phone camera makes it click.
- The modes are "Portrait", "Night", "Panorama".
- The shutter button does the same thing from your point of view: tap it, get a photo.
- The algorithm behind the shutter is swapped by the mode you picked.
- The button is the Context. The modes are the strategies. The button does not contain the night-mode math, it delegates.
This is composition over inheritance, live. Instead of a tall class tree with behavior baked in per subclass at compile time, you compose behavior by holding an object and swapping it. Three roles, that is it:
- Strategy: the interface that declares the one operation.
- Concrete Strategy: each implementation of that operation.
- Context: the class that holds a strategy and delegates to it.
3Structure#
The Context owns a reference to a Strategy, and it never builds one itself.
- You inject the strategy from outside, through the constructor or a setter.
- That injection is the whole trick: it is what lets you switch behavior at runtime.
- It is also what makes the Context testable: in a test you pass a fake strategy and assert the Context delegated to it.
Each Concrete Strategy implements the interface and nothing more.
- They do not know about each other.
- They often do not even know about the Context.
- They just do their one job, which is textbook Single Responsibility.
Here is the runtime flow when someone checks out with express shipping selected:
4Implementation in Java#
interface ShippingStrategy {
double cost(Order order);
}
class StandardShipping implements ShippingStrategy {
public double cost(Order order) {
return order.weight() * 0.5;
}
}
class ExpressShipping implements ShippingStrategy {
public double cost(Order order) {
return order.weight() * 0.5 + 10;
}
}
class OvernightShipping implements ShippingStrategy {
public double cost(Order order) {
return order.weight() * 0.5 + 25;
}
}
class Checkout {
private ShippingStrategy strategy;
Checkout(ShippingStrategy strategy) {
this.strategy = strategy;
}
void setStrategy(ShippingStrategy strategy) {
this.strategy = strategy;
}
double total(Order order) {
return order.subtotal() + strategy.cost(order);
}
}
Using it:
Checkout checkout = new Checkout(new StandardShipping());
double a = checkout.total(order); // standard rate
checkout.setStrategy(new ExpressShipping());
double b = checkout.total(order); // express rate, same object
What changed in Checkout when I needed a new shipping option? Nothing.
- You write a new class that implements ShippingStrategy and pass it in.
- The Checkout class never reopens.
- That is Open-Closed in practice, not in theory.
Two things worth saying out loud to an interviewer.
- Each strategy here is stateless, so you can reuse a single instance across every request (or make it a singleton). Nothing per-call to protect means it is thread-safe by default.
- The interface has a single abstract method, which makes it a functional interface in Java. For simple cases you can skip the classes entirely:
ShippingStrategy flatRate = order -> 7.99;
checkout.setStrategy(flatRate);
A lambda is a strategy. That is not a trick, it is the same pattern with less ceremony. Mention it, because it shows you understand the pattern is about the shape of the behavior, not the class count.
5A real-world example#
Payment processing is the clearest production case.
- A checkout flow supports cards, UPI, wallets, and bank transfer.
- Each has completely different code: different SDKs, different validation, different failure handling, different retry rules.
- Jam all of that into one processPayment with a switch, and every new method is surgery on critical money code, the code you least want to re-test under pressure.
Strategy makes each provider a class.
- Define PaymentMethod with a pay(amount) method.
- CardPayment, UpiPayment, WalletPayment each implement it.
- The order service holds whichever one the user selected.
- Add a new provider next quarter by adding one class and wiring it into the selection map. The order service does not change, which matters a lot when the thing you are afraid to break is the thing that takes money.
You have already been using Strategy in the standard library.
Comparatorin Java is pure Strategy:Collections.sort(list, comparator)takes the comparison algorithm as a parameter, so sorting itself does not care how two items compare.ThreadPoolExecutortakes aRejectedExecutionHandler, the strategy for "what do we do when the queue is full", and the JDK ships four:AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy.- Once you see the pattern, you see it everywhere.
6When to use it, and when not to#
Reveal the answer
Reach for Strategy when:
- You have several ways to do one thing and you pick between them at runtime or by configuration.
- You keep adding new variants and dread editing the same method each time.
- You want to test each variant in isolation, far easier when each is its own class with its own small test.
- You have a big conditional that switches purely on a type or a mode.
Skip it when:
- There are exactly two cases and they will never grow. A clean if-else is honestly fine.
- The variants are not really interchangeable: they need different inputs or return different shapes. If the method signature cannot stay the same across all of them, Strategy is fighting you.
- The behavior is fixed at compile time per subclass and never changes at runtime. That might just be ordinary inheritance.
The honest test: are these things genuinely swappable behind one signature, and do I expect the set to grow? Two yeses, use Strategy. Otherwise do not reach for it just because you can name it.
When I describe a system with "pluggable" or "configurable" behavior, I am fishing for this pattern. What I score is not whether you can recite the three roles, it is whether you reach for it at the right moment and refuse to reach for it at the wrong one. Naming when Strategy is overkill tells me more than naming when it fits.
7Common mistakes#
Building all the strategy classes, then writing if (type == CARD) strategy = new CardPayment() inside the Context. You moved the giant conditional, you did not delete it. The Context is supposed to receive a strategy, not decide which one. This is the single most common way candidates think they used Strategy and actually did not.
Letting the Context pick the strategy with a switch.
- Push that selection out to a factory, or a map keyed by type, or let the caller inject the choice.
- The Context should receive a strategy, not decide it.
Making strategies carry state they should not.
- Keep concrete strategies as close to pure functions as you can.
- A strategy that hoards mutable fields loses the ability to be shared safely across threads, and creates coupling where there was none.
A fat interface.
- Strategy is at its best with a single method, or a very tight set.
- If the interface grows to five methods and each concrete class implements two and stubs out the rest, that is the Interface Segregation Principle telling you the variants are not as interchangeable as you assumed.
Over-engineering trivial choices.
- The flip side: do not pattern-match your way into three files to decide between two constants.
Confusing it with State (the one interviewers probe on purpose).
- Strategy and State look identical on a class diagram. The difference is intent.
- Strategy variants are chosen from outside and do not know about each other.
- State objects swap themselves based on internal transitions.
- Same skeleton, different story. Be ready to say that out loud.
Same class diagram, different intent: Strategy is chosen from outside, State switches itself from within.
8Where it shows up in interviews#
Strategy is one of the highest-value patterns to have loaded, because it answers a whole family of prompts.
- Payment system: different payment methods behind one interface. Almost guaranteed to appear.
- Discount or pricing engine: percentage off, flat off, buy-one-get-one, each a strategy.
- Notification service: email, SMS, push, each a NotificationStrategy.
- Parking lot or ride-share fare calculation: different pricing rules per vehicle type or time of day.
- Sorting, routing, matchmaking: anywhere the "how" of a step is interchangeable.
The move that scores points: when you hit a conditional on type during a design, pause and say, "I will pull each of these into a strategy behind a common interface, so adding a new one does not touch this class." Then draw the three boxes and the aggregation arrow. That one sentence signals you think about change, not just the happy path. If they ask how you would pick the strategy at runtime, mention a factory or a registry map keyed by type, so the selection logic also lives in one swappable place.
Keep the vocabulary crisp: Strategy, Concrete Strategy, Context, injection, Open-Closed. Say those words, draw the aggregation arrow, and you have demonstrated the pattern cleanly in under two minutes.
"How is this different from the State pattern? They look identical."
"Where does the Context get the right strategy from, then?"
"Do I need a class per variant? That feels heavy for simple cases."
"Are these strategies thread-safe? Can I share one instance?"
"When would you not use Strategy here?"
What you can now explain in the room
- The intent of Strategy in one line, and the three roles: Strategy, Concrete Strategy, Context.
- Why injecting the strategy (not building it inside the Context) is what makes the pattern work and makes it testable.
- How Strategy satisfies Open-Closed: a new variant is a new class, not an edit to tested code.
- The classic misuse: hiding a switch inside the Context, which renames the conditional instead of removing it.
- When to skip Strategy, and why over-engineering a two-case choice is also a red flag.
- How Strategy differs from State on the same class diagram, by intent rather than shape.
Next, look at the Factory pattern (how you choose which strategy to hand in) and the State pattern (Strategy's near-twin), plus LLD problems like the payment gateway and the discount engine where this shows up front and center.
Get a prep plan built around your Strategy Pattern interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.