Hook: Interviewers love this one because it shows whether you can replace messy if-else logic with clean, swappable behavior.
Question: What is the Strategy Pattern?
Answer: The Strategy Pattern is a design pattern that lets you define a family of algorithms, put each one behind the same interface, and switch between them without changing the code that uses them. In Java, that usually means a Context class holds a reference to a Strategy interface and delegates work to the chosen implementation. It is useful when the behavior changes often, but the main workflow stays the same.
Interview-Ready Answer: I use the Strategy Pattern when I want one piece of code to support multiple interchangeable behaviors, like different discount rules or payment methods. Instead of hardcoding if-else branches, I define a common interface, implement each algorithm separately, and let a context object delegate to the selected strategy. That keeps the code open for extension but closed for modification, and it makes testing each algorithm much easier.
Detailed Explanation: A strategy is simply one way to do a task. The pattern says: “Put each way in its own class, give them the same contract, and choose one at runtime.” A contract is just an interface, which is a promise that the classes will provide the same method names and inputs.
RegularDiscount or BlackFridayDiscount.Context object, often through the constructor or a setter.Context stores the strategy behind the interface type, not the concrete class.Context calls the strategy method, for example calculate() or pay().Context code.The call is usually one extra interface dispatch, so the runtime cost is tiny: effectively O(1) per call, with one more object reference to store. The real cost is design cost: more classes and more indirection.
if-else or switch branches for one behavior.It shows you understand the Open/Closed Principle: software should be open for extension, but closed for modification. In plain words, you can add a new strategy without rewriting old code. It also shows you can separate policy from mechanism: the policy is the business rule, and the mechanism is the fixed flow that uses it.
| Pattern | Main idea | Runtime swap? | Typical use |
|---|---|---|---|
| Strategy | Choose one algorithm | Yes | Discounts, sorting, routing |
| State | Behavior changes with state | Yes | Game character modes, workflow states |
| Template Method | Fixed steps, variable steps | No | Base class defines process flow |
if-else | Hardcoded branching | Sometimes | Small, stable decisions |
Strategy vs State: Strategy is usually chosen by the client and can be swapped for business reasons; State is usually changed internally by the object as it moves through a lifecycle. Strategy vs Template Method: Strategy uses composition and interfaces; Template Method uses inheritance and overrides.
if-else may be clearer.Memory hook: Think of a restaurant menu: the kitchen stays the same, but you choose a different recipe card for each order.
Real-World Story: Imagine a checkout service in an e-commerce app. During normal days it applies a 5% discount, during a holiday sale it applies 20%, and for VIP users it may apply a custom rule. The checkout flow should not care which discount formula is used; it should only ask the chosen strategy for the final price.
What goes wrong when teams skip Strategy? They build a giant pricing method with nested if checks for region, user type, campaign, coupon type, and feature flags. Eventually a small change to one discount accidentally breaks another one, because all rules live in the same method and the same release.
Incident example: a Black Friday discount was added directly inside the checkout method, and the developer forgot that subscription renewals should not use it. Users saw wrong totals in the UI, logs showed conflicting discount messages like Applied seasonal discount and Applied renewal discount, and support tickets spiked because customers were charged less than expected or blocked by validation mismatches between frontend and backend totals.
The fix was to move each pricing rule into its own strategy and let the checkout service pick one based on the order type. After that, QA could test each rule independently, and a new promotion could be added without touching the existing ones.
import java.util.Objects;
public class StrategyPatternDemo {
// Strategy = interchangeable algorithm behind one common contract.
interface DiscountStrategy {
double apply(double subtotal);
}
// Default behavior: no discount. Useful when the caller has no special rule.
static class NoDiscount implements DiscountStrategy {
@Override
public double apply(double subtotal) {
return subtotal;
}
}
static class PercentageDiscount implements DiscountStrategy {
private final double percent; // e.g. 0.20 means 20%
PercentageDiscount(double percent) {
if (percent < 0.0 || percent > 1.0) {
throw new IllegalArgumentException("percent must be between 0.0 and 1.0");
}
this.percent = percent;
}
@Override
public double apply(double subtotal) {
return subtotal * (1.0 - percent);
}
}
static class FlatDiscount implements DiscountStrategy {
private final double amount;
FlatDiscount(double amount) {
if (amount < 0.0) {
throw new IllegalArgumentException("amount must be non-negative");
}
this.amount = amount;
}
@Override
public double apply(double subtotal) {
return Math.max(0.0, subtotal - amount); // edge case: never go below zero
}
}
// Context = stable workflow that delegates behavior to a chosen strategy.
static class Checkout {
private final DiscountStrategy discountStrategy;
Checkout(DiscountStrategy discountStrategy) {
// Fail fast or use a safe default. Here we choose a safe default.
this.discountStrategy = discountStrategy == null ? new NoDiscount() : discountStrategy;
}
double total(double subtotal) {
if (subtotal < 0.0) {
throw new IllegalArgumentException("subtotal must be non-negative");
}
return discountStrategy.apply(subtotal);
}
}
public static void main(String[] args) {
Checkout normalCheckout = new Checkout(new PercentageDiscount(0.05));
Checkout saleCheckout = new Checkout(new PercentageDiscount(0.20));
Checkout vipCheckout = new Checkout(new FlatDiscount(15.00));
Checkout fallbackCheckout = new Checkout(null); // edge case: no strategy provided
double subtotal = 100.00;
System.out.printf("Normal total: $%.2f%n", normalCheckout.total(subtotal));
System.out.printf("Sale total: $%.2f%n", saleCheckout.total(subtotal));
System.out.printf("VIP total: $%.2f%n", vipCheckout.total(subtotal));
System.out.printf("Fallback: $%.2f%n", fallbackCheckout.total(subtotal));
// Failure path: invalid input should not silently produce nonsense.
try {
System.out.printf("Bad total: $%.2f%n", normalCheckout.total(-10.0));
} catch (IllegalArgumentException ex) {
System.out.println("Rejected invalid subtotal: " + ex.getMessage());
}
// Another useful point: strategies can be swapped by creating a different context.
DiscountStrategy seasonal = new PercentageDiscount(0.30);
System.out.printf("Seasonal total: $%.2f%n", new Checkout(seasonal).total(80.0));
}
}Follow-up & Tricky Questions:
if-else? No. If the logic is tiny and stable, branching may be simpler and more readable. Strategy pays off when variation is real, likely to grow, or needs to be tested independently.Common Mistakes:
NoDiscount.Memory Hook: “Same kitchen, different recipe card.” The workflow stays fixed; only the recipe changes.
Cheat Sheet:
Practice Tasks:
if-else price calculator with Strategy.