ReviseAlgo Logo

Behavioral Patterns

Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients.

Last Updated: June 26, 2026 23 min read

Strategy Pattern — Swappable Algorithms

ShoppingCart
paymentStrategy: PaymentStrategy
checkout() { strategy.pay(total) }
swap at runtime
CreditCard
pay() → card gateway
PayPal
pay() → PayPal API
UPI
pay() → UPI gateway
cart.setStrategy(new CreditCard()); // → switch algorithm
cart.checkout(); // uses selected strategy

The Strategy Pattern (also known as Policy Pattern) is a behavioral design pattern that defines a family of algorithms, encapsulates each one of them inside a dedicated class, and makes them interchangeable. By delegating algorithm execution to a polymorphic variable, Strategy allows behavior to be swapped at runtime without modifying the client context class.

1. Learning Objectives

  • Identify and refactor complex conditional branches (switch/if-else chains) into swappable strategies.
  • Construct modular interfaces separating client invocation from algorithm execution details.
  • Evaluate the memory and performance impacts of stateless strategy reuse vs. stateful instance garbage collection.
  • Compare the structural differences and intentions of Strategy, State, and Template Method patterns.
  • Implement thread-safe, dynamic strategies in Java, Python, and C++ (using smart pointer composition).

2. Problem & Naive Solution

Suppose you are building a payment module for an e-commerce platform. The system supports multiple payment methods: Credit Card, PayPal, and Cryptocurrency.

The Naive Solution

A developer might write the payment processing logic directly inside the main ShoppingCart class using conditional checks:

This design introduces significant architectural issues:

  • Violates Open/Closed Principle: Adding a new payment method (e.g. Google Pay, Apple Pay) forces you to modify the core ShoppingCart class, risking bugs in existing payment flows.
  • Low Cohesion: The ShoppingCart class is bloated with vendor-specific socket setups, cryptographic signatures, and payment APIs, violating the Single Responsibility Principle.
  • Untestability: You cannot easily mock specific payment channels or test connection timeouts without executing the entire conditional flow.

3. Issues

Hardcoding algorithms inside the calling context locks execution statically. If payment processors update their API signatures or require different metadata schemas, you must rewrite the main application class, risking system-wide regressions.

4. Pattern Introduction & UML

The Strategy Pattern extracts the execution paths into separate classes (strategies). The context class (ShoppingCart) references a common PaymentStrategy interface. During checkout, the client injects the desired strategy object. The shopping cart invokes pay() on the interface reference, remaining decoupled from the actual implementation.

UML: Strategy Payment System

5. Participants

  • Context (ShoppingCart): Maintains a reference to a Strategy object and delegates the algorithm execution.
  • Strategy (PaymentStrategy): The common interface declaring the algorithm execution method contract.
  • Concrete Strategy (CreditCardPayment, PayPalPayment): Implements the specific algorithm (e.g. Stripe card charge, PayPal token redirect).

6. Theory (Polymorphism vs. Conditionals)

Strategy shifts the design model from conditional branches to dynamic runtime polymorphism:

  • Decoupled Logic: Algorithms are isolated. The client selects the strategy up-front and configures the context, removing nested logic branches.
  • Comparison: - Strategy: Swaps interchangeable algorithms dynamically. The client is aware of the strategy choices. - State: Swaps behaviors dynamically based on an object's internal state. The transitions are managed automatically inside the state classes without client intervention. - Template Method: Extends parts of an algorithm using subclass inheritance, binding the skeleton statically at compile-time.

7. Syntax Explanation

Syntax details for strategy delegation:

  • Java: Uses standard interface injection, or references Java 8 functional interfaces (PaymentStrategy acting as a @FunctionalInterface) enabling lambda definitions.
  • Python: Because functions are first-class citizens, you can pass function references directly to the context constructor as strategies, bypassing class boilerplate.
  • C++: Employs std::unique_ptr references, or uses std::function wrappers to bind callable objects (functors, lambdas) dynamically.

8. Step-by-Step Implementation

  1. Step 1: Create the Strategy interface containing execution methods (e.g. pay(double amount)).
  2. Step 2: Implement concrete strategy classes containing the specific algorithm details.
  3. Step 3: Create the Context class, exposing a setter method to inject or update the active strategy reference at runtime.
  4. Step 4: Inside the context execution methods, invoke the delegate method on the strategy reference.
  5. Step 5: Instantiate the context and configure it with the desired strategy dynamically in the client code.

9. Complete Code (Mini Project)

10. Code Walkthrough

Let's review the architectural elements:

  • Polymorphic Variable: Inside the ShoppingCart class, the paymentStrategy field references the parent interface, not a concrete implementation. This decouples the class from specific payment providers.
  • Algorithmic Separation: Each payment provider has its own class (CreditCardPayment, PayPalPayment). Subsystem API updates are isolated to these subclasses, keeping the core shopping cart code clean.
  • Dynamic Runtime Swap: The client can change the active payment method at any point by calling setPaymentStrategy() before executing checkout().

11. Execution Flow

  1. Configuration: The client instantiates the context (ShoppingCart) and a strategy (e.g. CreditCardPayment).
  2. Injection: The client passes the strategy reference into the context via setPaymentStrategy().
  3. Trigger: The client calls cart.checkout().
  4. Delegation: The context calls validate() and pay() on the injected strategy interface.
  5. Dynamic Resolution: The runtime system dispatches the call to the concrete strategy method, executing the selected payment flow.

12. Internal Working (JVM & Memory footprint)

Using strategies has runtime performance implications:

  • Vtable Dispatch Cost: Resolving strategy calls at runtime requires looking up method addresses in the virtual table (vtable). This introduces a minor CPU execution delay compared to direct static method binding.
  • Stateless vs Stateful Strategy Memory: - If strategies are *stateful* (e.g., storing user credit card numbers as instance variables), a new strategy object must be allocated on the heap for every checkout, increasing Garbage Collection churn. - If strategies are *stateless* (e.g., a routing algorithm that takes coordinates as parameters), you can share a single strategy instance (Singleton) across all threads, reducing memory usage to zero.

13. Complexity Analysis

  • Time Complexity: $O(1)$ constant time overhead to resolve the polymorphic strategy call.
  • Space Complexity: $O(1)$ constant memory overhead to hold references to the strategy classes.

14. Best Practices

  • Keep Strategies Stateless: Try to pass required parameters directly into strategy methods (e.g. pay(amount, cardDetails)) instead of storing them as fields, allowing strategy instances to be shared across threads.
  • Define default fallback strategies: Configure the context with a default strategy in its constructor to prevent NullPointerException errors if the client forgets to inject a strategy.

15. Common Mistakes

  • Forcing Strategy Classes on Lambdas: Writing boilerplate strategy classes for simple behaviors. In languages supporting functional programming (Java 8+, Python, C++11), use lambdas or function pointers instead of creating full class files.
  • Coupling Clients to Strategies: Forcing the client class to understand low-level strategy API options during runtime initialization.

16. Framework Usage

  • Java's Comparator: The java.util.Comparator interface is a classic Strategy pattern. It allows you to sort collections using different sorting strategies (e.g. string length vs alphabetical order) by passing a comparator instance to Collections.sort().
  • Spring Resource Loader: Spring's Resource abstraction uses different strategies (e.g. UrlResource, ClassPathResource, FileSystemResource) to load files based on the file path prefix.

17. Interview Discussion

Q: What is the key design difference between Strategy and State patterns?
Answer: - Strategy focuses on making algorithms interchangeable. The client selects the strategy up-front and injects it. - State focuses on swapping behavior based on an object's internal state. The state classes manage transitions automatically, meaning the client is often unaware of the state swaps.
Q: How do you pass context parameters to a strategy method?
Answer: - Option A: Pass specific parameters as arguments (e.g., pay(amount, email)). This keeps the strategy decoupled, but forces interface changes if a new strategy requires different parameters. - Option B: Pass the entire context object as a reference (e.g., pay(this)). This allows strategies to query the context for any details they need, but couples the strategy interface to the concrete context class.
Q: How does Java 8 lambda syntax simplify Strategy pattern implementations?
Answer: If the strategy interface has only one abstract method, it acts as a functional interface. This allows clients to define strategies inline using lambda expressions, eliminating the need to write separate class files for simple algorithms.

18. Practice Exercises

  • Easy: Write a Python program containing multiple sorting strategies (BubbleSort, QuickSort) implementing a unified sorting interface.
  • Medium: Design a TravelRoutePlanner containing strategies for driving directions, walking paths, and public transit routing.
  • Hard: Build a dynamic text compressor. The context accepts raw text and compresses it on-the-fly. Implement Zip, GZip, and Huffman coding strategies.

19. Challenge Problem

Design an Adaptive Video Streaming Bitrate Controller. The client application plays high-definition video. The video bitrate must adapt dynamically based on network bandwidth updates: High Bitrate (1080p), Medium Bitrate (720p), and Low Bitrate (360p). Implement this adaptive controller as a Strategy pattern. Write a solution in Java, Python, or C++ that checks network bandwidth every few seconds and swaps the video rendering strategy at runtime based on network speed changes.

20. Summary & Cheat Sheet

  • Strategy encapsulates a family of algorithms and makes them interchangeable at runtime.
  • Eliminates nested conditional blocks (if-else/switch branches) inside contexts.
  • Prefer stateless strategies to support instance sharing and reduce GC memory churn.
  • Use lambdas or functional interfaces to avoid writing separate class files for simple behaviors.

21. Quiz

1. What is the primary purpose of the Strategy design pattern?

A) To adapt incompatible interfaces
B) To define a family of algorithms, encapsulate each one, and make them interchangeable (Correct)
C) To control object creation lifecycles

2. Which design mechanism does Strategy use to decouple context from algorithms?

A) Object Composition (Correct)
B) Multiple Inheritance
C) Static global maps

3. What coding structure does Strategy primarily eliminate?

A) Recursive functions
B) Nested conditional branches (e.g. if-else / switch) (Correct)
C) Class inheritance

4. Why are stateless strategies preferred over stateful strategies?

A) They compile faster
B) They can be shared across multiple contexts, reducing memory allocation and GC churn (Correct)
C) They bypass vtable dynamic dispatch

5. Which of the following is a classic example of the Strategy pattern in Java?

A) java.util.Comparator (Correct)
B) java.lang.String
C) java.io.FileInputStream

6. How does Strategy differ from Template Method?

A) Strategy uses composition to swap algorithms at runtime; Template Method uses inheritance to vary parts of an algorithm at compile-time (Correct)
B) Strategy is only supported in Python; Template Method in C++
C) Strategy runs on separate threads; Template Method does not

7. What is the main drawback of passing the entire context object to a strategy method?

A) It triggers class memory leaks
B) It couples the strategy interface to the concrete context class, reducing reusability (Correct)
C) It disables virtual method lookups

8. Can a functional interface in Java be used to implement the Strategy pattern?

A) Yes, by using lambda expressions to define strategies inline without writing separate class files (Correct)
B) No, the pattern requires abstract classes
C) Only when using database connection pools

9. In C++, what smart pointer is best to hold the strategy reference inside the context class?

A) std::unique_ptr or std::shared_ptr (Correct)
B) std::weak_ptr
C) void* raw pointer

10. Does the Strategy pattern follow the Open/Closed Principle?

A) No, because you must edit the context to add a strategy
B) Yes, because you can introduce new strategies without modifying the context class (Correct)
C) Only when using Spring framework annotations

22. Next Lesson Preview

In the next lesson, we will explore the Iterator Pattern. We will learn how to access the elements of an aggregate object sequentially without exposing its underlying representation!