All chapters in this track
- 01Chapter 1: Why Patterns Exist — Vocabulary, Not a Checklist
- 02Chapter 2: Patterns and SOLID: How They Relate
- 03Chapter 3: Creational Patterns I: Factory Method & Abstract Factory
- 04Chapter 4: Creational Patterns II: Builder, Singleton, Prototype
- 05Chapter 5: Structural Patterns I: Adapter & Facade
- 06Chapter 6: Structural Patterns II: Decorator & Proxy
- 07Chapter 7: Structural Patterns III: Composite, Bridge & Flyweight
- 08Chapter 8: Behavioral Patterns I: Strategy & Template Method
- 09Chapter 9: Behavioral Patterns II: Observer & Mediator
- 10Chapter 10: Behavioral Patterns III: Command & Memento
- 11Chapter 11: Behavioral Patterns IV: State & Chain of Responsibility
- 12Chapter 12: Behavioral Patterns V: Iterator & Visitor
- 13Chapter 13: Anti-Patterns and Overuse: Recognizing "Pattern Fever"
- 14Chapter 14: How Dynamic and Functional Languages Solve These Problems Differently
- 15Chapter 15: Architectural Patterns I: MVC/MVVM & Layered/Hexagonal Architecture
- 16Chapter 16: Architectural Patterns II: Event-Driven Architecture & CQRS
- 17Chapter 17: Architectural Patterns III: The Repository Pattern
- 18Chapter 18: Recognizing Which Pattern Fits an Interview Design Prompt
Chapter 8 of 18
Chapter 8: Behavioral Patterns I: Strategy & Template Method
Strategy
The problem it solves: an algorithm needs to vary independently of the code that uses it, with each variant fully interchangeable — this was already introduced concretely in Chapter 2 (shipping cost calculation) as the direct embodiment of Open/Closed; here's a second worked example to reinforce the shape with a different flavor of problem: compression.
interface CompressionStrategy {
compress(data: string): string;
}
class GzipCompression implements CompressionStrategy {
compress(data: string): string { return `gzip(${data})`; }
}
class ZipCompression implements CompressionStrategy {
compress(data: string): string { return `zip(${data})`; }
}
class FileArchiver {
constructor(private strategy: CompressionStrategy) {} // injected — DIP in action
archive(data: string): string {
return this.strategy.compress(data);
}
setStrategy(strategy: CompressionStrategy): void {
this.strategy = strategy; // can even be swapped at runtime
}
}
const archiver = new FileArchiver(new GzipCompression());
console.log(archiver.archive("file contents")); // "gzip(file contents)"
archiver.setStrategy(new ZipCompression());
console.log(archiver.archive("file contents")); // "zip(file contents)" — no change to FileArchiver needed
from typing import Protocol
class CompressionStrategy(Protocol):
def compress(self, data: str) -> str: ...
class GzipCompression:
def compress(self, data: str) -> str:
return f"gzip({data})"
class FileArchiver:
def __init__(self, strategy: CompressionStrategy) -> None:
self._strategy = strategy
def archive(self, data: str) -> str:
return self._strategy.compress(data)
When to use it: multiple interchangeable algorithms exist (or will exist) for one operation, and callers shouldn't need to know which one is active — sorting comparators, pricing rules, validation rules, compression, payment processing.
When to avoid it: exactly one algorithm exists with no real evidence a second is coming — as with every pattern in this book, wait for the real need (Chapter 1's discipline).
Complexity cost: low — one interface, one class per variant. Among the cheapest patterns to introduce relative to the flexibility gained, which is part of why it's one of the most commonly (and sometimes over-eagerly) applied patterns in real codebases.
Template Method
The problem it solves: several related algorithms share the same overall sequence of steps, but differ in the details of one or more individual steps — you want to define the sequence once and let subclasses customize only the steps that actually vary.
Before — the same sequence duplicated, with only small variations:
class CsvReportGenerator {
generate(data: object[]): string {
const validated = this.validate(data);
const formatted = validated.map(row => JSON.stringify(row)).join(",");
return `CSV:${formatted}`;
}
private validate(data: object[]): object[] { return data.filter(d => d !== null); }
}
class XmlReportGenerator {
generate(data: object[]): string {
const validated = this.validate(data); // duplicated identically from CsvReportGenerator
const formatted = validated.map(row => `<row>${JSON.stringify(row)}</row>`).join("");
return `XML:${formatted}`;
}
private validate(data: object[]): object[] { return data.filter(d => d !== null); } // duplicated
}
After — Template Method factors out the shared sequence, leaving only the varying step to subclasses:
abstract class ReportGenerator {
// The template method: defines the fixed sequence, calling into steps subclasses customize.
generate(data: object[]): string {
const validated = this.validate(data);
return this.format(validated);
}
protected validate(data: object[]): object[] {
return data.filter(d => d !== null); // shared default — subclasses can still override if truly needed
}
protected abstract format(data: object[]): string; // the step that must vary per subclass
}
class CsvReportGenerator extends ReportGenerator {
protected format(data: object[]): string {
return `CSV:${data.map(row => JSON.stringify(row)).join(",")}`;
}
}
class XmlReportGenerator extends ReportGenerator {
protected format(data: object[]): string {
return `XML:${data.map(row => `<row>${JSON.stringify(row)}</row>`).join("")}`;
}
}
const csv = new CsvReportGenerator();
console.log(csv.generate([{ a: 1 }])); // "CSV:{"a":1}"
from abc import ABC, abstractmethod
class ReportGenerator(ABC):
def generate(self, data: list[dict]) -> str:
validated = self.validate(data)
return self.format(validated)
def validate(self, data: list[dict]) -> list[dict]:
return [d for d in data if d is not None]
@abstractmethod
def format(self, data: list[dict]) -> str: ...
class CsvReportGenerator(ReportGenerator):
def format(self, data: list[dict]) -> str:
return "CSV:" + ",".join(str(row) for row in data)
When to use it: several classes share an identical overall algorithm structure with variation isolated to specific, identifiable steps — this eliminates duplicated boilerplate around the varying logic.
When to avoid it: the "shared sequence" isn't actually stable — if the order of steps also needs to vary per case (not just individual step implementations), Template Method's fixed sequence becomes a constraint rather than a help, and Strategy (or a composed pipeline of steps) fits better.
Complexity cost: introduces inheritance (with its OOP-book Chapter 5 coupling costs — subclasses depend on the base class's exact sequencing), so it's worth checking whether the "favor composition" default should apply here instead of inheritance, especially if the varying step count grows.
Strategy vs. Template Method
[SENIOR INSIGHT] Both let variation happen without duplicating a whole algorithm, but the mechanism differs in exactly the way Chapter 7 of the OOP book distinguishes composition from inheritance: Strategy composes — the algorithm variant is a separate object, injected in, swappable at runtime. Template Method inherits — the algorithm's fixed sequence lives in a base class, and subclasses override specific steps, fixed at compile time via the class hierarchy. If you find a Template Method's subclasses each overriding a different, unrelated single step and never needing to be swapped at runtime, that's fine as-is; if you find yourself wanting to swap which "subclass" is active at runtime, or needing steps to vary independently of the fixed order, that's a signal to convert to Strategy (or compose several small strategies as pipeline steps) instead.
Common mistakes
- Choosing Template Method when runtime swapping is actually needed. Inheritance fixes the variant at compile time via the class hierarchy; if the algorithm variant genuinely needs to change at runtime, Strategy is the correct tool, not Template Method.
- Choosing Strategy when the "algorithm" is really just a single interchangeable step inside a larger fixed sequence shared by everyone — that's Template Method's shape, not Strategy's; using Strategy there means re-implementing the shared sequence in every strategy, duplicating exactly what Template Method exists to avoid.
- Introducing either pattern for a single implementation with no real second case in sight — the same premature-pattern warning as every chapter so far.
- Letting a Template Method's base class accumulate too many customizable steps, turning it into a fragile-base-class risk (OOP book, Chapter 5/12) as more and more hooks are added for more and more subclasses' edge cases.
Discussion / interview questions
-
"What's the difference between Strategy and Template Method, given both let algorithm behavior vary?" Strong answer: Strategy composes a swappable algorithm object, chosen/injected and changeable at runtime; Template Method fixes the overall sequence in a base class and lets subclasses override specific steps, fixed at compile time via inheritance.
-
"When would you convert a Template Method design into a Strategy-based one?" Strong answer: When runtime swapping of the variant is needed, when the "steps" need to vary independently of a fixed order, or when the inheritance coupling (OOP book, Chapter 5) between the base class and its growing set of subclasses becomes a maintenance problem.
-
"Why is Strategy often considered one of the safest, most broadly applicable patterns?" Strong answer: Low complexity cost (one interface, one class per variant), directly embodies Open/Closed, uses composition rather than inheritance (avoiding the OOP book's fragile-base-class risk) — but still should only be introduced with a real, current need for interchangeable variants.
-
"What's a risk of a Template Method base class accumulating too many overridable steps over time?" Strong answer: It risks becoming a fragile base class (OOP book, Chapters 5 and 12) — each new overridable hook increases the coupling surface between the base class and every subclass, and a change to the shared sequence can silently break subclasses relying on the old step order or defaults.
Chapter summary / cheat sheet
- Strategy: composes a swappable algorithm object; runtime-flexible; low complexity cost; directly embodies Open/Closed.
- Template Method: fixes a shared algorithm sequence in a base class, letting subclasses override specific steps; compile-time fixed via inheritance; eliminates duplicated boilerplate around varying steps.
- Choose based on whether runtime swapping is needed (Strategy) or the sequence itself is stable and shared, with only specific steps varying (Template Method).
- Both require a real, current need for variation before being introduced — the same discipline applied throughout this book.