• Home /
  • Gen AI /
  • The Strategy Design Pattern: Stop Writing if-else Chains Forever

The Strategy Design Pattern: Stop Writing if-else Chains Forever

In this blog, we’ll understand how the Strategy Design Pattern in Java helps you replace messy if-else chains with a cleaner and more scalable approach—both in LLD interviews and real-world systems.

Picture this scenario.

You are shipping a feature for your e-commerce app. Users can now pay via credit card, UPI, or net banking. 

So you write it:

public void processPayment(String type, double amount) {
    if (type.equals("CREDIT_CARD")) {
        // validate card, call Stripe, log transaction...
    } else if (type.equals("UPI")) {
        // validate VPA, call UPI gateway...
    } else if (type.equals("NET_BANKING")) {
        // bank list, redirect, callback...
    }
    // next quarter: add PayPal -> edit this file again
    // then crypto -> edit this file again
}

It ships. Life is good — for three months. Then the product team adds two more payment methods. Then a third. The method balloons to 285 lines. Nobody wants to touch it anymore. One wrong keystroke in the UPI block can silently break the credit card flow. Your unit tests are a nightmare.

⚠ Before Strategy Pattern — What Your Code Becomes

if (type == "CREDIT_CARD") { ... 40 lines ... }
else if (type == "UPI") { ... 35 lines ... }
else if (type == "NET_BANKING") { ... 50 lines ... }
else if (type == "PAYPAL") { ... 45 lines ... } ← added Q2
else if (type == "CRYPTO") { ... 60 lines ... } ← added Q3
else if (type == "BNPL") { ... 55 lines ... } ← added Q4

Touch one branch, pray the others survive.

This is the exact problem the Strategy Pattern was designed to solve.
"Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it."

— Gang of Four, Design Patterns (1994)

💡 The Real Insight

Strategy Pattern is not about adding classes. It’s about removing decisions from your code.

If your code keeps asking “what type is this?” at runtime — through instanceof, string comparisons, or enum switches — that’s your codebase screaming for Strategy. Every if (type == X) is a decision that could live inside a class instead.

What Is the Strategy Pattern?

The Strategy pattern is a behavioural design pattern. That word “behavioural” is key — it is about how objects communicate and how responsibilities are distributed, not about how they are created (Creational) or how they are structured (Structural).

The core idea: pull each variant of an algorithm out of a conditional chain, wrap it in its own class, and plug it in through a shared interface. The caller — called the Context — never needs to know which specific algorithm is running. It just calls execute().

📌 NOTE

The Strategy pattern is one of the most commonly asked Low-Level Design (LLD) questions at top product companies like Amazon, Flipkart, and Razorpay. If you have an SDE interview in your future, this pattern is not optional. 

The Participants — A Visual Map

Before we touch code, let us get the vocabulary right. The Strategy pattern has exactly three moving parts:

Role

Responsibility

In our payment example

Context

Holds a reference to the active strategy. Delegates execution to it.

PaymentProcessor

Strategy (interface)

Defines the contract — what every algorithm must be able to do.

PaymentStrategy

Concrete Strategy

One class per algorithm variant. Implements the interface.

CreditCardStrategy, UpiStrategy

✗ Before Strategy
One bloated method
✓ After Strategy
Plug-and-play algorithms
All algorithms crammed into if-else Each algorithm = its own class
One change can break everything Changes are fully isolated
New payment = edit existing file New payment = add one new file
Can’t unit test algorithms in isolation Each strategy is independently testable
Grows without bound — forever Context stays lean — always

Building It Step by Step

Step 1: Define the Strategy Interface

First, we identify the common contract. Every payment method needs to do two things: validate the input and process the payment. That goes into an interface.

PaymentStrategy.java

// The shared contract -- every payment method must implement this
public interface PaymentStrategy {
    boolean validate(PaymentRequest request);
    PaymentResult pay(double amount, PaymentRequest request);
}
Step 2: Write a Concrete Strategy per Algorithm

Each payment method becomes its own class. It only knows about its own logic. Changes to UPI logic cannot possibly break the credit card class — they live in completely separate files.

CreditCardStrategy.java 

public class CreditCardStrategy implements PaymentStrategy {

    private final StripeGateway stripe;

    public CreditCardStrategy(StripeGateway stripe) {
        this.stripe = stripe;
    }

    @Override
    public boolean validate(PaymentRequest req) {
        // Luhn check, expiry validation, CVV length...
        return LuhnAlgorithm.check(req.getCardNumber());
    }

    @Override
    public PaymentResult pay(double amount, PaymentRequest req) {
        return stripe.charge(amount, req.getToken());
    }
}

UpiStrategy.java 

public class UpiStrategy implements PaymentStrategy {

    private final NpciGateway npci;

    @Override
    public boolean validate(PaymentRequest req) {
        // VPA format check: user@bank
        return req.getVpa().matches("^[a-zA-Z0-9.]+@[a-zA-Z]+$");
    }

    @Override
    public PaymentResult pay(double amount, PaymentRequest req) {
        return npci.initiateTransfer(req.getVpa(), amount);
    }
}
Step 3: Build the Context

The Context holds a reference to whichever strategy is active. It delegates the work without caring about the implementation details. This is the entire magic of the pattern.

PaymentProcessor.java (Context) 

public class PaymentProcessor {

    private PaymentStrategy strategy; // set at runtime

    // Strategy is injected -- not hardcoded
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public PaymentResult processPayment(double amount, PaymentRequest request) {
        if (!strategy.validate(request)) {
            throw new InvalidPaymentException("Validation failed");
        }
        return strategy.pay(amount, request);
        // Works identically for CreditCard, UPI, BNPL, Crypto -- anything
    }
}
Scroll to Top