Bird
Raised Fist0
LLDsystem_design~15 mins

Payment strategy pattern in LLD - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Payment strategy pattern
What is it?
The Payment Strategy Pattern is a design approach that lets a system choose different ways to pay without changing its core logic. It separates payment methods like credit card, PayPal, or bank transfer into independent parts called strategies. This makes adding or changing payment options easy and safe. It helps systems handle payments flexibly and cleanly.
Why it matters
Without this pattern, payment code mixes all payment methods together, making it hard to add new ones or fix bugs. Imagine a store that wants to add a new payment option but must rewrite big parts of its code every time. This slows development and risks errors. The pattern solves this by isolating payment methods, so the system can switch or add them without breaking anything.
Where it fits
Before learning this, you should understand basic programming concepts like functions and classes, and know what design patterns are. After this, you can learn about other behavioral patterns like the Observer or Command pattern, or explore how to design scalable payment systems with security and fault tolerance.
Mental Model
Core Idea
Separate payment methods into interchangeable parts so the system can pick any way to pay without changing its main code.
Think of it like...
It's like a universal remote control that can operate different devices by switching the mode, without needing a new remote for each device.
┌─────────────────────┐
│    Payment System    │
│  (Context/Client)    │
└─────────┬───────────┘
          │ uses
          ▼
┌─────────────────────┐
│  Payment Strategy    │◄─────────────┐
│  (Interface/Abstract)│              │
└─────────┬───────────┘              │
          │ implements                 │
          ▼                          ▼
┌───────────────┐           ┌───────────────┐
│ CreditCardPay │           │  PayPalPay    │
│  (Strategy)   │           │  (Strategy)   │
└───────────────┘           └───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding payment methods
🤔
Concept: Payment methods are different ways customers pay for goods or services.
Common payment methods include credit cards, debit cards, digital wallets like PayPal, and bank transfers. Each method has its own rules and steps to complete a payment. For example, credit cards require card number and expiry date, while PayPal needs user authentication.
Result
You recognize that payment methods vary and have unique processing steps.
Understanding the diversity of payment methods is key to designing a flexible system that can handle them all.
2
FoundationWhat is a design pattern?
🤔
Concept: A design pattern is a reusable solution to a common problem in software design.
Design patterns help organize code to be easier to maintain and extend. They are like templates or blueprints that solve typical challenges, such as how to manage different behaviors or how to create objects.
Result
You know that design patterns improve code quality and adaptability.
Knowing design patterns prepares you to apply proven solutions instead of inventing new ones each time.
3
IntermediateIntroducing the strategy pattern
🤔Before reading on: do you think the strategy pattern changes the main code for each new behavior or keeps it unchanged? Commit to your answer.
Concept: The strategy pattern lets you define a family of algorithms, encapsulate each one, and make them interchangeable.
Instead of writing all payment logic in one place, the strategy pattern creates separate classes for each payment method. The main system picks which strategy to use at runtime. This keeps the main code clean and open to new payment methods without changes.
Result
You can add or switch payment methods without touching the core payment system.
Understanding that behavior can be swapped dynamically without modifying core logic is powerful for flexible system design.
4
IntermediateImplementing payment strategies
🤔Before reading on: do you think each payment strategy should know about others or work independently? Commit to your answer.
Concept: Each payment strategy implements the same interface but handles its own payment details independently.
Define a PaymentStrategy interface with a method like pay(amount). Then create classes like CreditCardPayment and PayPalPayment that implement this interface. The payment system holds a reference to a PaymentStrategy and calls pay(amount) without caring which one it is.
Result
Payment methods are isolated, making the system easier to test and extend.
Knowing that strategies are independent reduces bugs and makes adding new methods safe.
5
AdvancedDynamic strategy selection
🤔Before reading on: do you think the payment method should be fixed at compile time or chosen at runtime? Commit to your answer.
Concept: The system can choose the payment strategy dynamically based on user input or context.
At runtime, the system can ask the user which payment method to use, then create the corresponding strategy object. This allows the same system to support many payment options without code changes. For example, if user selects PayPal, the system uses PayPalPayment strategy.
Result
The payment system becomes flexible and user-friendly, adapting to different payment choices.
Understanding runtime selection unlocks real-world flexibility needed in payment systems.
6
AdvancedExtending with new payment methods
🤔Before reading on: do you think adding a new payment method requires changing existing code or just adding new code? Commit to your answer.
Concept: New payment methods can be added by creating new strategy classes without modifying existing ones.
To add a new method like ApplePay, create an ApplePayPayment class implementing PaymentStrategy. The main system does not change. This follows the Open/Closed Principle: open for extension, closed for modification.
Result
The system grows without risk of breaking existing payment flows.
Knowing how to extend without modifying existing code prevents regressions and supports continuous delivery.
7
ExpertHandling complex payment workflows
🤔Before reading on: do you think the strategy pattern alone handles retries, logging, and security, or do you need extra design? Commit to your answer.
Concept: The strategy pattern focuses on payment method selection but complex workflows require combining it with other patterns or components.
In real systems, payments involve retries, logging, fraud checks, and security. These concerns can be handled by decorators, middleware, or separate services. The strategy pattern isolates payment logic but does not replace these cross-cutting concerns. Combining patterns leads to robust, maintainable payment systems.
Result
You understand the limits of the pattern and how to build production-ready payment systems.
Knowing the pattern's scope prevents over-reliance and encourages holistic system design.
Under the Hood
The payment system holds a reference to a PaymentStrategy interface. At runtime, it assigns a concrete strategy object implementing this interface. When pay(amount) is called, the system delegates the call to the chosen strategy's implementation. This delegation hides the payment details from the system, enabling interchangeable behaviors.
Why designed this way?
This pattern was designed to separate varying behaviors from stable code, reducing code duplication and increasing flexibility. Before this, systems used conditional statements to handle different payment methods, which became hard to maintain as methods grew. The strategy pattern replaces conditionals with polymorphism, a cleaner and more scalable approach.
┌─────────────────────┐
│   Payment System    │
│  (Context/Client)   │
└─────────┬───────────┘
          │ holds
          ▼
┌─────────────────────┐
│ PaymentStrategy (I) │
└───────┬─────────────┘
        │
 ┌──────┴────────┐
 │               │
▼               ▼
CreditCardPay   PayPalPay
(ConcreteStrategy)
Myth Busters - 4 Common Misconceptions
Quick: Does the strategy pattern require changing the main payment system code for each new payment method? Commit yes or no.
Common Belief:You must modify the main payment system code every time you add a new payment method.
Tap to reveal reality
Reality:The main system code stays unchanged; you only add new strategy classes implementing the payment interface.
Why it matters:Changing main code often leads to bugs and slows down adding new payment methods.
Quick: Is the strategy pattern only useful for payment systems? Commit yes or no.
Common Belief:The strategy pattern is only for payment processing scenarios.
Tap to reveal reality
Reality:It is a general pattern useful anywhere you want to swap algorithms or behaviors dynamically.
Why it matters:Limiting the pattern to payments prevents applying it to other problems like sorting, compression, or routing.
Quick: Does the strategy pattern handle cross-cutting concerns like logging or retries by itself? Commit yes or no.
Common Belief:The strategy pattern automatically manages retries, logging, and security for payments.
Tap to reveal reality
Reality:It only handles selecting payment methods; other concerns need separate patterns or components.
Why it matters:Assuming it handles everything leads to incomplete designs and fragile systems.
Quick: Can payment strategies depend on each other directly? Commit yes or no.
Common Belief:Payment strategies can call each other to share code or data.
Tap to reveal reality
Reality:Strategies should be independent to avoid tight coupling and maintain flexibility.
Why it matters:Coupled strategies create complex dependencies that are hard to maintain and extend.
Expert Zone
1
Some payment methods require asynchronous processing or callbacks, which means strategies must support async interfaces or event handling.
2
Strategies can be combined with the Factory pattern to create payment objects dynamically based on configuration or user input.
3
In distributed systems, payment strategies might delegate to external services, requiring careful error handling and circuit breakers.
When NOT to use
Avoid the strategy pattern when payment methods share almost identical logic with minor differences; in such cases, simpler conditional logic or parameterization might be better. Also, if payment methods require heavy cross-cutting concerns tightly integrated, consider using middleware or pipeline patterns instead.
Production Patterns
In real systems, the strategy pattern is combined with dependency injection frameworks to manage payment strategies. Logging, monitoring, and security layers wrap around strategies. Payment gateways often expose APIs that strategies call, and fallback strategies handle failures gracefully.
Connections
Factory pattern
Builds-on
Using the Factory pattern to create payment strategies dynamically complements the strategy pattern by managing object creation cleanly.
Middleware pattern
Complementary
Middleware handles cross-cutting concerns like logging and retries, which the strategy pattern alone does not cover.
Behavioral economics
Analogous pattern
Just as people choose different payment methods based on context and incentives, the strategy pattern models this choice in software, reflecting decision-making processes studied in behavioral economics.
Common Pitfalls
#1Mixing payment logic inside the main system instead of separating strategies.
Wrong approach:class PaymentSystem { pay(amount, method) { if (method === 'card') { processCard(amount); } else if (method === 'paypal') { processPayPal(amount); } // more methods... } }
Correct approach:interface PaymentStrategy { pay(amount); } class CardPayment implements PaymentStrategy { pay(amount) { /* card logic */ } } class PayPalPayment implements PaymentStrategy { pay(amount) { /* PayPal logic */ } } class PaymentSystem { constructor(strategy) { this.strategy = strategy; } pay(amount) { this.strategy.pay(amount); } }
Root cause:Not understanding how to separate varying behaviors into independent classes.
#2Hardcoding payment strategy selection inside the system.
Wrong approach:class PaymentSystem { constructor() { this.strategy = new CardPayment(); // fixed } pay(amount) { this.strategy.pay(amount); } }
Correct approach:class PaymentSystem { setStrategy(strategy) { this.strategy = strategy; } pay(amount) { this.strategy.pay(amount); } } // At runtime paymentSystem.setStrategy(new PayPalPayment());
Root cause:Not designing for runtime flexibility and dynamic behavior.
#3Allowing payment strategies to depend on each other directly.
Wrong approach:class PayPalPayment { constructor(cardPayment) { this.cardPayment = cardPayment; } pay(amount) { this.cardPayment.pay(amount); /* then PayPal steps */ } }
Correct approach:class PayPalPayment { pay(amount) { /* PayPal logic only */ } }
Root cause:Misunderstanding independence principle of strategies.
Key Takeaways
The Payment Strategy Pattern separates payment methods into independent classes, enabling flexible and maintainable payment processing.
It allows the system to choose or add payment methods at runtime without changing core logic, following the Open/Closed Principle.
Strategies must be independent and implement a common interface to ensure interchangeability and reduce coupling.
The pattern focuses on behavior selection but does not handle cross-cutting concerns like logging or retries, which require other patterns.
Combining the strategy pattern with factories and middleware leads to robust, scalable payment systems used in real-world applications.

Practice

(1/5)
1. What is the main benefit of using the Payment Strategy Pattern in a payment system?
easy
A. It allows switching between different payment methods without changing the main code.
B. It forces all payment methods to use the same currency.
C. It stores all payment data in a single database table.
D. It encrypts payment information automatically.

Solution

  1. Step 1: Understand the purpose of the Payment Strategy Pattern

    The pattern is designed to let the system switch payment methods easily without modifying the main logic.
  2. Step 2: Analyze the options

    Only It allows switching between different payment methods without changing the main code. describes this benefit correctly. Other options describe unrelated features.
  3. Final Answer:

    It allows switching between different payment methods without changing the main code. -> Option A
  4. Quick Check:

    Payment Strategy Pattern = Switch payment methods easily [OK]
Hint: Focus on flexibility to switch payment methods without code changes [OK]
Common Mistakes:
  • Confusing strategy pattern with data storage or encryption
  • Thinking it enforces currency or database rules
  • Assuming it handles security automatically
2. Which of the following is the correct way to define a payment strategy interface in a typical object-oriented language?
easy
A. interface PaymentStrategy { void pay(double amount); }
B. class PaymentStrategy { void pay(amount); }
C. function PaymentStrategy(amount) { return pay; }
D. var PaymentStrategy = pay => amount;

Solution

  1. Step 1: Identify the correct syntax for an interface

    In object-oriented languages, interfaces declare method signatures without implementation. interface PaymentStrategy { void pay(double amount); } uses 'interface' and a method signature correctly.
  2. Step 2: Check other options

    class PaymentStrategy { void pay(amount); } is a class, not an interface. Options C and D use function syntax, not interface definitions.
  3. Final Answer:

    interface PaymentStrategy { void pay(double amount); } -> Option A
  4. Quick Check:

    Interface syntax = interface PaymentStrategy { void pay(double amount); } [OK]
Hint: Look for 'interface' keyword and method signature format [OK]
Common Mistakes:
  • Using class instead of interface for strategy definition
  • Confusing function syntax with interface
  • Missing method parameter types
3. Given the following code snippet implementing the Payment Strategy Pattern, what will be the output?
class PaymentStrategy {
  pay(amount) { throw 'Not implemented'; }
}

class CreditCardPayment extends PaymentStrategy {
  pay(amount) { return `Paid ${amount} with Credit Card`; }
}

class PayPalPayment extends PaymentStrategy {
  pay(amount) { return `Paid ${amount} with PayPal`; }
}

class PaymentContext {
  constructor(strategy) { this.strategy = strategy; }
  executePayment(amount) { return this.strategy.pay(amount); }
}

const context = new PaymentContext(new PayPalPayment());
console.log(context.executePayment(100));
medium
A. Paid 100 with Credit Card
B. Not implemented
C. Paid 100 with PayPal
D. Error: strategy.pay is not a function

Solution

  1. Step 1: Trace the object creation and method calls

    The PaymentContext is created with a PayPalPayment strategy. Calling executePayment(100) calls PayPalPayment's pay method.
  2. Step 2: Understand the pay method output

    PayPalPayment's pay returns 'Paid 100 with PayPal'. This string is printed.
  3. Final Answer:

    Paid 100 with PayPal -> Option C
  4. Quick Check:

    Context uses PayPalPayment = Output with PayPal [OK]
Hint: Check which strategy instance is passed to context [OK]
Common Mistakes:
  • Assuming default or CreditCardPayment is used
  • Expecting an error from base class
  • Confusing method override behavior
4. Identify the error in the following Payment Strategy Pattern implementation:
class PaymentStrategy {
  pay(amount) { console.log('Paying ' + amount); }
}

class BitcoinPayment extends PaymentStrategy {
  pay() { console.log('Paying with Bitcoin'); }
}

const payment = new BitcoinPayment();
payment.pay(50);
medium
A. PaymentStrategy should not have a pay method implementation.
B. BitcoinPayment's pay method does not accept the amount parameter.
C. BitcoinPayment should not extend PaymentStrategy.
D. Calling pay with 50 causes a syntax error.

Solution

  1. Step 1: Compare method signatures in base and subclass

    PaymentStrategy's pay expects an amount parameter, but BitcoinPayment's pay method does not accept any parameters.
  2. Step 2: Understand the impact of signature mismatch

    Calling payment.pay(50) passes an argument, but BitcoinPayment's pay ignores it, causing unexpected behavior or errors.
  3. Final Answer:

    BitcoinPayment's pay method does not accept the amount parameter. -> Option B
  4. Quick Check:

    Method signature mismatch = BitcoinPayment's pay method does not accept the amount parameter. [OK]
Hint: Check if subclass methods match base method parameters [OK]
Common Mistakes:
  • Thinking base class should not implement pay
  • Assuming inheritance is wrong
  • Confusing runtime error with syntax error
5. You are designing a payment system that must support credit cards, PayPal, and a new cryptocurrency payment method. Using the Payment Strategy Pattern, which design approach best supports adding the new method with minimal changes?
hard
A. Use a global variable to switch payment methods inside the main payment function.
B. Modify the existing CreditCardPayment class to handle cryptocurrency payments.
C. Add cryptocurrency payment logic inside the PaymentContext class directly.
D. Create a new class implementing the PaymentStrategy interface for cryptocurrency and pass it to the payment context.

Solution

  1. Step 1: Understand the open/closed principle in design

    The system should be open for extension but closed for modification. Adding a new payment method should not require changing existing classes.
  2. Step 2: Evaluate each option

    Create a new class implementing the PaymentStrategy interface for cryptocurrency and pass it to the payment context. creates a new class implementing the interface, fitting the pattern and minimizing changes. Options B and C modify existing classes, violating the principle. Use a global variable to switch payment methods inside the main payment function. uses a global variable, which is poor design.
  3. Final Answer:

    Create a new class implementing the PaymentStrategy interface for cryptocurrency and pass it to the payment context. -> Option D
  4. Quick Check:

    New class for new method = Create a new class implementing the PaymentStrategy interface for cryptocurrency and pass it to the payment context. [OK]
Hint: Add new payment as new class, avoid changing existing code [OK]
Common Mistakes:
  • Modifying existing payment classes
  • Adding logic inside context class
  • Using global variables for strategy switching