Strategy — interchangeable algorithms selected at runtime, Java and Spring implementations (map-of-beans, lambdas), benefits, the payment-method example — and Template Method — a fixed algorithm skeleton with overridable steps, implementation with final template methods and hooks, examples (Spring's JdbcTemplate, AbstractController-style bases), limitations, and Strategy vs Template Method.
Published September 25, 2026
Strategy and Template Method solve the same problem, varying part of an algorithm, in opposite ways:
Senior interviewers expect you to compare them. They also expect you to know that since Java 8, many strategies are just lambdas, and that Spring's *Template classes are mostly callback-based strategies, despite the name.
Short answer: Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable, so the algorithm can vary independently of the client that uses it. It's typically used for payment methods, pricing or discount rules, sorting and comparison, compression, routing or shipping-cost calculations, and retry or backoff policies.
Learn it in depth → Strategy Pattern
Short answer: Template Method defines the skeleton of an algorithm in a base-class method. Subclasses redefine certain steps without changing the overall structure. It's typically used for data importers (read, parse, validate, save) where only the parsing differs between CSV, XML and JSON, report generation, and framework lifecycle hooks.
Learn it in depth → Template Method
Short answer: Use it when there are several ways to do one task, the choice is made at runtime (by configuration, user input or data), and you want to avoid a growing if/else or switch over types in the client. Each variant becomes its own class, or a lambda, behind a common interface.
Key points to cover:
switch (type) appears in several places.Short answer:
| Strategy | Template Method | |
|---|---|---|
| Mechanism | Composition: the context holds a strategy object | Inheritance: a subclass overrides steps |
| What varies | The whole algorithm | Some steps within a fixed algorithm |
| When it's chosen | At runtime, and it can be swapped | At compile time, fixed by the subclass |
| Coupling | Loose: the context knows only the interface | Tight: subclasses depend on the base-class internals |
| Java 8+ | Often just a lambda | Needs a class hierarchy |
Prefer Strategy (composition) by default. Use Template Method when the ordering and invariants must be enforced by a base class, and the variation points are small.
Common trap: the source's answer to "how does Template Method differ from Strategy?" (DP-094) only describes Template Method. Always state both sides, and the composition-vs-inheritance difference.
Short answer:
In Spring, inject all implementations as a map, and select by key. That removes the switch entirely.
public interface PaymentStrategy {
PaymentMethod method();
PaymentResult pay(Order order, PaymentDetails details);
}
@Component class CardPayment implements PaymentStrategy {
public PaymentMethod method() { return PaymentMethod.CARD; }
public PaymentResult pay(Order o, PaymentDetails d) { /* card gateway */ return PaymentResult.ok(); }
}
@Component class UpiPayment implements PaymentStrategy {
public PaymentMethod method() { return PaymentMethod.UPI; }
public PaymentResult pay(Order o, PaymentDetails d) { /* UPI collect request */ return PaymentResult.pending(); }
}
@Service
public class PaymentService { // the context
private final Map<PaymentMethod, PaymentStrategy> strategies;
public PaymentService(List<PaymentStrategy> all) {
this.strategies = all.stream().collect(Collectors.toUnmodifiableMap(PaymentStrategy::method, s -> s));
}
public PaymentResult pay(Order order, PaymentDetails details) {
PaymentStrategy s = strategies.get(details.method());
if (s == null) throw new UnsupportedPaymentMethodException(details.method());
return s.pay(order, details); // no switch: add a method = add a bean
}
}
Key points to cover:
Comparator<T>, Function<Order, BigDecimal>;list.sort(Comparator.comparing(Order::total).reversed()) is Strategy.Short answer:
The costs:
Short answer: Payment processing (above). Another common one is shipping-cost calculation: flat rate, weight-based, distance-based, a free-shipping promo, and a per-carrier API quote. Without Strategy, one method grows a switch with carrier-specific branches, which every change touches. With Strategy:
Short answer: Use it when several classes share the same process, and only specific steps differ, and you want the base class to guarantee the order and the common behaviour: opening and closing resources, validation, logging, metrics, transactions. Examples:
Short answer: Write an abstract class with a final template method that calls the steps in order. Make the steps abstract (mandatory) or hooks (overridable methods with default behaviour). Subclasses override only the steps.
public abstract class DataImporter<T> {
public final ImportReport run(Path file) { // the template method: final, so the order can't change
List<String> lines = read(file);
List<T> records = lines.stream().map(this::parse).toList();
List<T> valid = records.stream().filter(this::isValid).toList(); // hook with a default
save(valid);
afterImport(valid); // optional hook
return new ImportReport(records.size(), valid.size());
}
protected List<String> read(Path file) { // shared step
try { return Files.readAllLines(file); } catch (IOException e) { throw new UncheckedIOException(e); }
}
protected abstract T parse(String line); // mandatory steps
protected abstract void save(List<T> records);
protected boolean isValid(T record) { return true; } // hooks
protected void afterImport(List<T> records) { }
}
public class CustomerCsvImporter extends DataImporter<Customer> {
private final CustomerRepository repo;
public CustomerCsvImporter(CustomerRepository repo) { this.repo = repo; }
protected Customer parse(String line) { String[] f = line.split(","); return new Customer(f[0], f[1]); }
protected void save(List<Customer> cs) { repo.saveAll(cs); }
@Override protected boolean isValid(Customer c) { return c.email().contains("@"); }
}
Key points to cover:
final, and the steps protected, so subclasses can't break the sequence, and clients can't call steps out of order.Short answer: A data-processing application that ingests from files, databases and APIs. The workflow (connect, fetch, parse, validate, save, and report) is identical. Only the fetching and parsing differ, so each source is a subclass that overrides those two steps.
Key points to cover:
AbstractList/AbstractMap in the JDK: implement get/size, and inherit the rest.HttpServlet.service(), which dispatches to the doGet/doPost you override.@BeforeEach, the test, @AfterEach).ItemReader/ItemProcessor/ItemWriter steps.AbstractRoutingDataSource (override determineCurrentLookupKey()).JdbcTemplate/RestTemplate/TransactionTemplate fix the workflow, but take the variable part as a callback (RowMapper, TransactionCallback). That's the strategy/callback variant of the same idea, without inheritance.Short answer:
When the variation grows, refactor to Strategy: pass the steps in as collaborators or lambdas.
Short answer: Keep the fixed workflow in a concrete class, and inject the variable steps. This is exactly how JdbcTemplate works.
public final class Importer {
public <T> ImportReport run(Path file, Function<String, T> parser, Predicate<T> validator, Consumer<List<T>> saver) {
List<T> records = readLines(file).stream().map(parser).toList();
List<T> valid = records.stream().filter(validator).toList();
saver.accept(valid);
return new ImportReport(records.size(), valid.size());
}
}
importer.run(path, CustomerCsv::parse, c -> c.email().contains("@"), customerRepo::saveAll);
Q: Strategy vs State? A: They have the same structure: a context delegates to an interface. In Strategy, the client picks the algorithm, and the strategies don't know each other. In State, the state objects themselves trigger transitions to other states, as the context's internal state changes (an order moving from Created to Paid to Shipped).
Q: How do you select a strategy without a switch statement?
A: Use a Map<Key, Strategy> built from injected beans; have each strategy declare supports(input) and pick the first match; or use an enum whose constants each implement the strategy's method.
Q: Enum-based strategies: when are they appropriate?
A: When the set of algorithms is closed and small, and the algorithms are stateless. For example, enum Operation { PLUS { int apply(...) }, MINUS { ... } }. For an open-ended set, or algorithms with dependencies, use Spring beans.
Q: Why prefer composition over inheritance here? A: Composition can be swapped at runtime, combined across several dimensions, and tested with mocks, and it avoids fragile base classes. Inheritance is fine when the base class truly owns an invariant workflow, and the variation points are few and stable.