What Is the Strategy Pattern?
The Strategy pattern defines a family of interchangeable algorithms, encapsulates each one in its own class, and lets client code swap between them at runtime through a common interface. Instead of embedding conditional logic that picks behavior based on type checks, the context object holds a reference to a Strategy interface and delegates the actual work to whichever concrete implementation was injected, so new algorithms can be added without touching existing code.
Cricket analogy: A captain choosing between a leg-spinner like Rashid Khan and a pacer like Bumrah for the death overs is swapping bowling strategies without changing the match rules themselves, exactly how a Context swaps ConcreteStrategy objects.
Structure and Participants
The pattern has three participants: the Context, which maintains a reference to a Strategy object and delegates to it; the Strategy interface, which declares a common method such as execute() or calculate(); and one or more ConcreteStrategy classes that implement that method with a specific algorithm. The Context is typically configured via constructor injection or a setter, so the choice of algorithm can be made at object-creation time or changed dynamically during the object's lifetime.
Cricket analogy: The team management (Context) hands the ball to whichever bowler (ConcreteStrategy) implements the 'deliver an over' interface, and can switch bowlers between overs without redesigning the scoreboard.
interface PaymentStrategy {
pay(amountCents: number): string;
}
class CreditCardStrategy implements PaymentStrategy {
constructor(private cardNumber: string) {}
pay(amountCents: number): string {
return `Charged ${amountCents} cents to card ending ${this.cardNumber.slice(-4)}`;
}
}
class UpiStrategy implements PaymentStrategy {
constructor(private upiId: string) {}
pay(amountCents: number): string {
return `Debited ${amountCents} cents via UPI id ${this.upiId}`;
}
}
class Checkout {
private strategy: PaymentStrategy;
constructor(strategy: PaymentStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: PaymentStrategy) {
this.strategy = strategy;
}
completeOrder(amountCents: number): string {
return this.strategy.pay(amountCents);
}
}
const checkout = new Checkout(new CreditCardStrategy('4242424242424242'));
console.log(checkout.completeOrder(1999));
checkout.setStrategy(new UpiStrategy('user@bank'));
console.log(checkout.completeOrder(4599));When to Use It vs Alternatives
Strategy shines when a class has several variants of an algorithm that would otherwise require a large conditional block (if/else or switch) scattered through the codebase, especially when new variants get added over time. Replacing that conditional with polymorphism satisfies the Open/Closed Principle: adding a new strategy means writing a new class, not editing existing branching logic, which reduces regression risk in code that's already been tested.
Cricket analogy: Instead of an umpire mentally running through a giant if/else for every possible dismissal type, each dismissal (LBW, caught, run-out) is handled by its own reviewable rule, so adding a new technology-assisted rule like the 'soft signal' doesn't touch the existing ones.
The Open/Closed Principle (the 'O' in SOLID) states classes should be open for extension but closed for modification. Strategy is one of the clearest implementations of this: adding behavior means adding a class, not editing a tested one.
Strategy vs State Pattern
Strategy and State share the same UML shape — a context delegating to a pluggable object through a common interface — but they differ in intent and lifecycle. In Strategy, the client typically picks the algorithm once and it stays fixed for that context's lifetime, reflecting a client-driven choice; in State, the state objects themselves trigger transitions to other states in response to events, so the context's behavior evolves autonomously as the object moves through its lifecycle.
Cricket analogy: Choosing a bowler for an over (Strategy) is a captain's one-off tactical call, whereas a match's state — first innings, second innings, super over — transitions on its own as overs are bowled, without anyone 'choosing' the next phase.
If your 'ConcreteStrategy' classes start referencing each other and switching themselves based on internal conditions, you're no longer implementing Strategy — you've drifted into State. Rename and restructure so transition logic is explicit.
- Strategy encapsulates interchangeable algorithms behind one interface, letting a Context swap behavior via composition rather than conditionals.
- The three participants are Context, Strategy interface, and one or more ConcreteStrategy classes.
- It replaces sprawling if/else or switch blocks with polymorphism, satisfying the Open/Closed Principle.
- Strategies should be stateless and unaware of each other; they don't trigger transitions to other strategies.
- The Context can be configured with a strategy at construction time or reassigned dynamically via a setter.
- Strategy and State share the same structural shape but differ in intent: client-chosen algorithm vs self-transitioning behavior.
- A common tell that you've built State instead of Strategy is when 'strategies' start switching themselves based on internal events.
Practice what you learned
1. What is the primary intent of the Strategy pattern?
2. In the Strategy pattern, which participant holds a reference to the currently selected algorithm and delegates work to it?
3. Which SOLID principle does replacing a large conditional with Strategy most directly satisfy?
4. What is the key structural difference between Strategy and State, even though their UML diagrams look nearly identical?
5. A codebase has a payment method chosen once at checkout and never changed afterward, with new payment types added every few months. Which pattern best fits, and why?
Was this page helpful?
You May Also Like
State Pattern
The State pattern lets an object change its behavior when its internal state changes by delegating to interchangeable State objects that manage their own transitions.
Command Pattern
The Command pattern turns a request into a standalone object with a uniform execute() method, decoupling the invoker from the receiver and enabling undo, queuing, and logging.
Template Method Pattern
The Template Method pattern fixes the skeleton of an algorithm in a base class while letting subclasses override specific steps, using inheritance and the Hollywood Principle.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringSoftware Engineering Study Notes
Python · 40 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics
Software EngineeringSystem Design Study Notes
Architecture · 40 topics