SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignStrategy Pattern
Software DesignBehavioral Patterns

Strategy Pattern

Define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.

December 25, 20245 min read
design-patternsjavastrategybehavioral

One-line Definition

Extracts interchangeable algorithms into separate classes, allowing the client to swap behaviors dynamically at runtime.


Problem

Suppose you are building an e-commerce checkout system. You need to calculate discounts. Initially, you write logic for a SeasonalDiscount. Then marketing demands a ClearanceDiscount, a VIPDiscount, and a FlatRateDiscount. If you shove all this logic into the Checkout class using a massive switch statement, the class becomes bloated, fragile, and impossible to test independently. The Strategy Pattern solves this by moving each discount algorithm into its own class, letting the Checkout class swap them out cleanly at runtime.


Real-world Analogy

Think of navigating to the airport using Google Maps. The map is the Context. You can choose the Driving Strategy, the Walking Strategy, or the Public Transit Strategy. The map's core job is to display a route, but it delegates the actual pathfinding math to whichever strategy you select. You can swap strategies mid-journey without breaking the app.


Structure

  • Client
    • Creates a specific Strategy object and passes it to the Context.
  • Context
    • Maintains a reference to a Strategy object and delegates execution to it.
  • Strategy
    • Interface common to all supported algorithms.
  • ConcreteStrategy
    • Implements a specific algorithm following the Strategy interface.

Diagram

classDiagram
    class Client {
    }
    class Context {
        -Strategy strategy
        +setStrategy(Strategy)
        +executeStrategy()
    }
    class Strategy {
        <<interface>>
        +execute(data)
    }
    class ConcreteStrategyA {
        +execute(data)
    }
    class ConcreteStrategyB {
        +execute(data)
    }
    
    Client --> Context
    Client --> ConcreteStrategyA
    Context o-- Strategy : delegates
    ConcreteStrategyA ..|> Strategy
    ConcreteStrategyB ..|> Strategy

Code Walkthrough

Notice how the ShoppingCart (Context) has zero hardcoded logic for payment processing. It simply delegates the pay() action to whatever PaymentStrategy was injected by the client.

interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardStrategy implements PaymentStrategy {
    private final String cardNumber;

    public CreditCardStrategy(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid ₹" + amount + " using Credit Card ending in " + cardNumber.substring(12));
    }
}

class UPIStrategy implements PaymentStrategy {
    private final String upiId;

    public UPIStrategy(String upiId) {
        this.upiId = upiId;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid ₹" + amount + " using UPI ID: " + upiId);
    }
}

class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    private int amount = 0;

    public void addItem(int price) {
        amount += price;
    }

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout() {
        if (paymentStrategy == null) {
            System.out.println("Please select a payment method.");
            return;
        }
        paymentStrategy.pay(amount);
    }
}

class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.addItem(1500);
        cart.addItem(300);

        // Client chooses the UPI strategy dynamically
        cart.setPaymentStrategy(new UPIStrategy("user@ybl"));
        cart.checkout();

        // Swap strategy at runtime without altering the cart
        cart.setPaymentStrategy(new CreditCardStrategy("1111222233334444"));
        cart.checkout();
    }
}

Bad vs Good

Bad Approach

Problems

  • The ShoppingCart class is bloated with the business logic for every single payment gateway.
  • Adding a new payment method requires modifying the ShoppingCart, breaking the Open/Closed Principle.
class ShoppingCart {
    public void checkout(String method, int amount) {
        if (method.equals("CREDIT_CARD")) {
            // 50 lines of credit card logic
        } else if (method.equals("UPI")) {
            // 50 lines of UPI logic
        } else if (method.equals("PAYPAL")) {
            // 50 lines of PayPal logic
        }
    }
}

Better Approach

Improvements

  • The ShoppingCart is entirely decoupled from payment logic.
  • New payment methods are added simply by implementing an interface.
  • Favoring composition (Strategy) over inheritance prevents rigid, unchangeable hierarchies.
// Pure delegation. ShoppingCart delegates heavy lifting to the Strategy.
public void checkout() {
    paymentStrategy.pay(amount);
}

Pros vs Cons

ProsCons
Completely eliminates massive conditional statements (switch/if)Clients must understand the differences between strategies to pick the right one
Allows swapping algorithms dynamically at runtimeCan dramatically increase the number of classes in a project
Strictly enforces the Open/Closed PrincipleIn modern languages, simple strategies can be replaced with Lambdas/Functions
Isolates complex, volatile algorithm logic from the main Context
Favors Composition over Inheritance

When to Use

  • You have multiple variations of an algorithm (e.g., sorting, routing, pricing, compression) and want to swap them at runtime.
  • You want to isolate the business logic of an algorithm from the class that uses it.
  • Your class contains a massive conditional operator that switches between different behaviors.

When Not to Use

  • You only have a couple of algorithms and they rarely change.
  • The differences between the algorithms are trivial (e.g., just a single variable changing). Modern Java can handle this effortlessly with java.util.function.Consumer or Lambdas.

Real-world Examples

  • java.util.Collections#sort(List, Comparator) (The Comparator is a Strategy!).
  • Layout Managers in Java Swing (BorderLayout, FlowLayout).
  • Spring's Resource instantiation strategies or Spring Security's PasswordEncoder.

Key Takeaway

The Strategy Pattern replaces hard-coded conditional logic with interchangeable behavior objects. By delegating an operation to a Strategy interface, a Context class can effortlessly swap its behavior at runtime. Use it when dealing with families of algorithms (sorting, payment, validation), but leverage Lambdas in modern languages to avoid class explosion for simple strategies.

PreviousCommand PatternNextIterator Pattern