Problem
Growing conditional branches couple the checkout flow to every payment provider and make change risky.
OBJECT LAB / BEHAVIORAL PATTERN
Encapsulate interchangeable behavior behind one contract, then choose the implementation at runtime.
Checkout.pay()PaymentContext.execute()strategy.pay()PaymentResultGrowing conditional branches couple the checkout flow to every payment provider and make change risky.
The context delegates behavior to a shared strategy interface without knowing provider-specific details.
For one stable algorithm, separate strategy objects add indirection without earning meaningful flexibility.
The Strategy pattern defines a family of interchangeable algorithms, puts each one in its own class behind a shared interface, and lets the client choose one at runtime. The code that uses the algorithm (the context) does not know which concrete strategy it holds.
It replaces growing if/else or switch blocks that select behavior by type. Adding a new behavior becomes adding a new class instead of editing tested code, which is the Open-Closed Principle in action.
The goal is fixed: reach the airport. You can take a car, a train, or a taxi. You choose based on budget and time, and the rest of your trip plan does not change. Each option is a strategy.
Recognize it when: several ways to do the same task, chosen at runtime.
Recognize it when: switch on type keeps getting new cases.
Sorting takes a comparison strategy: Collections.sort(list, comparator).
Authentication strategies (local, Google, GitHub) plug into the same login flow.
PasswordEncoder implementations (bcrypt, argon2) are swappable strategies.
Identify the switch or if/else chain that selects an algorithm.
Extract one method signature that every variant can implement.
Each case becomes a concrete strategy with its own dependencies and tests.
Pass the strategy into the context through its constructor or a setter.
A factory or map chooses the strategy from input, keeping selection logic in one place.
A checkout that supports card, UPI, and wallet payments
| Concern | Without Strategy | With Strategy |
|---|---|---|
| Adding PayPal | Edit Checkout's switch statement | Add a PayPalPayment class |
| Testing | Test every branch through Checkout | Test each strategy alone |
| Checkout knows | Every provider's API details | Only PaymentStrategy.pay() |
| Risk of change | Can break existing methods | Existing classes untouched |
NOWConcern: Adding PayPal | Without Strategy: Edit Checkout's switch statement | With Strategy: Add a PayPalPayment class
The context depends on an abstraction, and new behavior is added by extension. Selection still happens somewhere, but in one small factory instead of inside business logic.
interface PaymentStrategy { pay(amount: number): PaymentResult;} type PaymentResult = { success: boolean; fee: number; reference: string }; class CardPayment implements PaymentStrategy { constructor(private cardNumber: string) {} pay(amount: number): PaymentResult { const fee = amount * 0.029; return { success: true, fee, reference: `CARD-${this.cardNumber.slice(-4)}` }; }} class UpiPayment implements PaymentStrategy { constructor(private upiId: string) {} pay(amount: number): PaymentResult { return { success: true, fee: 0, reference: `UPI-${this.upiId}` }; }} class Checkout { constructor(private strategy: PaymentStrategy) {} setStrategy(strategy: PaymentStrategy) { this.strategy = strategy; // swap at runtime } complete(amount: number): PaymentResult { return this.strategy.pay(amount); // no idea which provider }} const checkout = new Checkout(new CardPayment("4111111111111111"));checkout.complete(128);checkout.setStrategy(new UpiPayment("user@bank"));checkout.complete(128);Negligible compared with the work the strategy performs.
Plus one interface.
For two stable variants, a simple if statement is clearer. Use Strategy when variants are growing or tested separately.
Someone still selects the strategy. Keep that in a factory or registry rather than scattering it.
In TypeScript, Python, or Java 8+, a function type or lambda can replace a class when the strategy has no state.
Both delegate to an interface. In Strategy, the client picks the behavior; in State, the object switches its own behavior as its state changes.
Template Method varies steps through inheritance; Strategy varies the whole algorithm through composition.
A Map<string, Strategy> lets configuration or plugins add strategies without modifying code.
Fix: Pass a shared context object or supply extra data through the strategy's constructor.
Fix: Keep selection in a factory so the context stays closed to modification.
Fix: Wait until a second real variant exists (YAGNI).
New behavior is added as a new class implementing the interface. The context and existing strategies are not modified.
Intent and who switches. Strategy is chosen externally for how to do something; State represents what mode the object is in and transitions internally.
| Problem | Difficulty | What it trains |
|---|---|---|
| Discount engine | Easy | Percentage, flat, and buy-one-get-one strategies. |
| Parking fee calculator | Medium | Hourly, daily, and weekend pricing. |
| Pluggable compression tool | Medium | Registry of zip, gzip, and none. |