Opening low-level design
Progress is saved as you study.
Opening low-level design
Progress is saved as you study.
Opening your learning path
Progress is saved as you study.
Strategy Pattern is a core lesson in the Strategy & Factory Patterns family of the Thita low level design course.
#1 and #4 most tested patterns: eliminate if/else chains with Strategy and create objects without specifying exact classes with Factory.
It is part of the Interview Soon path: the LLD material that appears in a standard interview loop rather than only in a specialist one.
Use Strategy when a class chooses behavior with branches like if (type == CREDIT_CARD) ... else if (type == UPI) ....
The problem starts when one class holds many algorithms. Every new payment method, pricing rule, or allocation rule forces edits to the same class. Old branches need retesting each time.
Strategy moves each algorithm into its own class. The main class depends on an interface and calls the selected algorithm through that interface.
A common first version of payment code looks like this:
1if (type == CREDIT_CARD) {
2 // validate card
3 // charge card
4} else if (type == UPI) {
5 // validate UPI
6 // debit UPI
7} else if (type == WALLET) {
8 // check wallet balance
9 // debit wallet
10}Two payment methods may look harmless. The design gets harder to change when the interviewer adds wallets, rewards, net banking, refunds, retries, fraud checks, and country rules.
PaymentProcessor now has too many reasons to change. Every new payment method requires editing the same class. Each edit can break code that already worked.
Open/Closed Principle means stable code should stay closed for edits when you add new behavior.
| Design | Adding UPI | Adding Wallet | Risk |
|---|---|---|---|
if/else inside processor | Modify processor | Modify processor again | Existing flows may break |
| Strategy Pattern | Add UpiPayment class | Add WalletPayment class | Existing strategies stay untouched |
| Strategy + Factory | Add class + registration | Add class + registration | Creation stays separate from execution |
In interviews, type-based branching is the smell. Use Strategy when each branch is a different algorithm for the same operation.
Strategy defines a family of algorithms. Each algorithm gets its own class. The context calls the selected algorithm through an interface.
For payments, the algorithm family is “how to pay.” Credit card, UPI, and wallet are separate strategies. PaymentProcessor only calls pay(amount).
PaymentProcessor starts with an if/else per payment type, then gains a PaymentStrategy interface and one class per method, and takes its strategy by constructor.
| Component | Responsibility | Payment Example |
|---|---|---|
| Strategy interface | Declares the operation all algorithms support | PaymentStrategy.pay(amount) |
| Concrete strategy | Implements one algorithm | CreditCardPayment, UpiPayment |
| Context | Holds a strategy reference and delegates work | PaymentProcessor |
Inject the strategy into the context, usually through the constructor. The caller knows which behavior is needed.
Think “Strategy” when you see a branch on a type and each branch runs a different algorithm.
Common triggers:
CREDIT_CARD, UPI, WALLET, NET_BANKINGAsk: “Are these branches different ways to perform the same operation?” If yes, Strategy usually fits.
Use Strategy only when behavior varies.
| Situation | Use Strategy? | Better Choice |
|---|---|---|
| One payment method only | No | Simple method |
| Two algorithms already requested | Yes | Strategy |
| Branches represent object lifecycle phases | No | State Pattern |
| Creation logic is complex | Add more than Strategy | Factory + Strategy |
A strong interview answer names the boundary: use Strategy where behavior varies independently from the object using it.
1public class Main {
2 interface PaymentStrategy {
3 void pay(int amount);
4 }
5
6 static class CreditCardPayment implements PaymentStrategy {
7 private final String cardNumber;
8 CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; }
9
10 @Override
11 public void pay(int amount) {
12 System.out.println("Paid " + amount + " using card " + cardNumber);
13 }
14 }
15
16 static class UpiPayment implements PaymentStrategy {
17 private final String upiId;
18 UpiPayment(String upiId) { this.upiId = upiId; }
19
20 @Override
21 public void pay(int amount) {
22 System.out.println("Paid " + amount + " using UPI " + upiId);
23 }
24 }
25
26 static class PaymentProcessor {
27 private final PaymentStrategy strategy;
28 PaymentProcessor(PaymentStrategy strategy) { this.strategy = strategy; }
29 void process(int amount) { strategy.pay(amount); }
30 }
31
32 public static void main(String[] args) {
33 PaymentProcessor processor = new PaymentProcessor(new UpiPayment("user@upi"));
34 processor.process(500);
35 }
36}Constructor injection keeps payment execution separate from payment selection. PaymentProcessor receives the strategy and calls it.
Parking lot design is a common LLD interview case. A weak version puts allocation rules inside ParkingLot with branches like if (vehicleType) or if (policy). The class keeps growing when the interviewer asks for nearest-first, cheapest-first, or electric-charging-first allocation.
Move allocation into a strategy. ParkingLot asks an AllocationStrategy to select a spot.
1import java.util.*;
2
3public class Main {
4 interface AllocationStrategy {
5 int allocate(List<Integer> spots);
6 }
7
8 static class NearestSpotStrategy implements AllocationStrategy {
9 @Override
10 public int allocate(List<Integer> spots) {
11 return spots.get(0);
12 }
13 }
14
15 static class FarthestSpotStrategy implements AllocationStrategy {
16 @Override
17 public int allocate(List<Integer> spots) {
18 return spots.get(spots.size() - 1);
19 }
20 }
21
22 static class ParkingLot {
23 private final AllocationStrategy strategy;
24 ParkingLot(AllocationStrategy strategy) { this.strategy = strategy; }
25 int park(List<Integer> availableSpots) { return strategy.allocate(availableSpots); }
26 }
27
28 public static void main(String[] args) {
29 ParkingLot lot = new ParkingLot(new NearestSpotStrategy());
30 System.out.println("Allocated spot " + lot.park(Arrays.asList(1, 2, 3)));
31 }
32}Allocation strategies are easy to test. You can test NearestSpotStrategy without building the full parking lot system.
Strategy and State both use an interface with multiple implementations. The intent separates them.
Strategy is chosen by the client. State changes inside the object as the object moves through its lifecycle.
For payment, the user chooses UPI or credit card. That is Strategy.
For an order moving from PLACED to SHIPPED to DELIVERED, the order changes its own status. That is State.
| Question | Strategy | State |
|---|---|---|
| Who chooses implementation? | Client or caller | Object itself |
| Why does it change? | User preference or policy | Lifecycle transition |
| Payment example | UPI vs credit card | Payment pending vs completed |
| Order example | Delivery pricing algorithm | Order status lifecycle |
Use Strategy when the algorithm acts like a plug-in. Use State when behavior changes because the object’s lifecycle changes.
Use Strategy for interchangeable behavior. Use Factory for object construction. When both problems exist, use Factory to create the Strategy and inject it into the Context.
In LLD interviews, use Strategy when you see if/else or switch on a type and each branch is a different algorithm for the same operation.
Steps:
if/else inside the context and only moving code into helper methods. Method size drops, but OCP still fails.PaymentProcessor. Selection logic becomes hidden, and tests get harder.PaymentStrategy with unrelated methods like refund, validateKyc, sendOtp, and calculateReward. Keep the interface focused on the operation the context needs.Strategy is a design pattern for maintainability and testing. It does not make a slow algorithm fast.
| Metric | Typical Value | Interview Meaning |
|---|---|---|
| Strategy selection | O(1) if already injected | No algorithmic penalty |
| Strategy execution | Cost of chosen algorithm | Pattern does not change algorithm complexity |
| Extra memory | One strategy object/reference | Usually a few bytes to small object size |
| Virtual/interface dispatch | O(1) | Usually negligible unless the code runs in a tight CPU loop |
| Threads added | 0 | Strategy does not imply concurrency |
| Testing impact | One strategy test per algorithm | Smaller tests, fewer branch-heavy tests |
| Modern single-machine RAM | 512 GiB general-purpose | Do not justify Strategy using scalability claims |
| Modern CPU | 128 vCPUs general-purpose | Design clarity matters more than dispatch overhead |
In interviews, say the runtime overhead is constant and small. The design benefit is isolated change.