8 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Template Method Pattern
1. What Is It?
The pattern defines the skeleton of an algorithm in a base class, deferring specific steps to subclasses. The base class controls the overall sequence of steps — what happens and in what order — while subclasses supply the concrete implementations of individual steps. The algorithm's structure is fixed; only the details vary.
Without it, you end up with duplicated algorithm scaffolding spread across multiple classes. Each variant re-implements the full flow, and a bug in the shared logic has to be fixed in every copy. The inverts this: the invariant structure lives in one place, and only the variant pieces are distributed to subclasses.
A team maintains three report-generation classes — PDF, CSV, and HTML — each implementing the same sequence: fetch data, transform it, and write output. A bug is found in the data-fetching logic and must be patched in all three classes. Which design change would best prevent this kind of repeated fix in the future?
2. How It Works
- An abstract base class defines the — a regular (often
final) method that calls a sequence of steps. - Some steps are abstract methods — subclasses must implement them.
- Other steps are hook methods — they have default (often empty) implementations that subclasses may optionally override.
- The base class calls its own abstract/hook methods from inside the , ensuring the algorithm sequence never changes.
Concrete example — data parsing pipeline:
The steps are always: (1) read raw data, (2) parse it, (3) analyze it, (4) write results. The format (CSV vs. JSON vs. XML) changes; the pipeline does not.
Python implementation:
from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Any @dataclass class RawData: content: str @dataclass class ParsedData: records: list[dict[str, Any]] @dataclass class AnalysisResult: summary: str class DataProcessor(ABC): """Template method: defines the fixed algorithm skeleton.""" def process(self) -> None: """The template method — do not override this.""" raw = self.read_data() parsed = self.parse_data(raw) result = self.analyze_data(parsed) self.before_write() # hook — optional override self.write_result(result) @abstractmethod def read_data(self) -> RawData: ... @abstractmethod def parse_data(self, raw: RawData) -> ParsedData: ... @abstractmethod def analyze_data(self, parsed: ParsedData) -> AnalysisResult: ... @abstractmethod def write_result(self, result: AnalysisResult) -> None: ... def before_write(self) -> None: """Hook: override to add pre-write logic. Default: no-op.""" pass class CsvDataProcessor(DataProcessor): def read_data(self) -> RawData: return RawData(content="name,age\nAlice,30\nBob,25") def parse_data(self, raw: RawData) -> ParsedData: lines = raw.content.strip().split("\n") headers = lines[0].split(",") records = [ dict(zip(headers, line.split(","))) for line in lines[1:] ] return ParsedData(records=records) def analyze_data(self, parsed: ParsedData) -> AnalysisResult: count = len(parsed.records) return AnalysisResult(summary=f"Processed {count} CSV records") def write_result(self, result: AnalysisResult) -> None: print(f"[CSV] {result.summary}") def before_write(self) -> None: print("[CSV] Flushing buffer before write") # hook override class JsonDataProcessor(DataProcessor): def read_data(self) -> RawData: return RawData(content='[{"name": "Alice"}, {"name": "Bob"}]') def parse_data(self, raw: RawData) -> ParsedData: import json return ParsedData(records=json.loads(raw.content)) def analyze_data(self, parsed: ParsedData) -> AnalysisResult: return AnalysisResult(summary=f"Processed {len(parsed.records)} JSON records") def write_result(self, result: AnalysisResult) -> None: print(f"[JSON] {result.summary}") # Usage if __name__ == "__main__": for processor in [CsvDataProcessor(), JsonDataProcessor()]: processor.process()
Java implementation:
// --- Abstract base --- public abstract class DataProcessor { /** Template method — final so subclasses cannot reorder the steps. */ public final void process() { RawData raw = readData(); ParsedData parsed = parseData(raw); AnalysisResult result = analyzeData(parsed); beforeWrite(); // hook writeResult(result); } protected abstract RawData readData(); protected abstract ParsedData parseData(RawData raw); protected abstract AnalysisResult analyzeData(ParsedData parsed); protected abstract void writeResult(AnalysisResult result); /** Hook method — subclasses may override; default is no-op. */ protected void beforeWrite() {} } // --- Concrete subclass --- public class CsvDataProcessor extends DataProcessor { @Override protected RawData readData() { return new RawData("name,age\nAlice,30\nBob,25"); } @Override protected ParsedData parseData(RawData raw) { String[] lines = raw.getContent().split("\n"); String[] headers = lines[0].split(","); List<Map<String, String>> records = new ArrayList<>(); for (int i = 1; i < lines.length; i++) { String[] values = lines[i].split(","); Map<String, String> record = new LinkedHashMap<>(); for (int j = 0; j < headers.length; j++) { record.put(headers[j], values[j]); } records.add(record); } return new ParsedData(records); } @Override protected AnalysisResult analyzeData(ParsedData parsed) { return new AnalysisResult("Processed " + parsed.getRecords().size() + " CSV records"); } @Override protected void writeResult(AnalysisResult result) { System.out.println("[CSV] " + result.getSummary()); } @Override protected void beforeWrite() { System.out.println("[CSV] Flushing buffer before write"); } }
A backend team is building a data export pipeline using the Template Method pattern. The abstract base class defines a final export() method that calls fetchData(), transformData(), validateOutput(), and writeToDestination() in that order. The team wants validateOutput() to be completely optional — most exporters won't need it, but a few might want to add custom validation logic. Which approach best implements this requirement?
3. Variants & Comparisons
vs. :
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Template Method | Inheritance — subclass overrides steps | Algorithm structure is enforced; low overhead | Requires subclassing; harder to swap at runtime | Fixed pipeline with variant steps |
| Strategy | Composition — delegate algorithm to injected object | Swappable at runtime; no inheritance needed | More classes; client must know which strategy | Fully interchangeable algorithms |
| Hook Methods | Optional override in base class | Flexible extension points without forcing overrides | Hooks can be missed or misused | Optional customization in a pipeline |
Abstract method vs. hook method:
- Abstract method: subclass must provide an implementation — the step is mandatory.
- Hook method: base class provides a default (often empty) — subclass may override. Hooks are "opt-in" extension points.
final in Java: Marking the template method final prevents subclasses from accidentally reordering the algorithm steps. This is idiomatic Java for this pattern.
Python note: Python has no final keyword natively, but typing.final (PEP 591) signals intent. The convention is to document that the method should not be overridden.
A backend team has a data export pipeline where the overall sequence of steps (validate → transform → serialize → write) must always execute in exactly that order, but different export formats (CSV, JSON, XML) each need a different serialization step. A new requirement arrives: the pipeline should also support an optional post-write notification step that most formats will skip. Which combination of techniques best addresses both requirements?
4. When to Use It (and When NOT To)
Use it when:
- Multiple classes share the same algorithm skeleton but differ in specific steps.
- You want to control the invariant sequence centrally, preventing subclasses from reordering steps.
- You need optional extension points (hooks) without forcing overrides.
Do NOT use it when:
- The algorithm itself varies widely between subclasses — prefer instead ( over ).
- You need to swap implementations at runtime — requires different subclasses, not runtime injection.
- The hierarchy is already deep — adding another level creates fragile base class risk.
Decision triggers:
- "If you see the same multi-step pipeline repeated across several classes with only step internals differing → reach for ."
- "If you see the order of steps also needs to vary → use or Chain of Responsibility instead."
- "If only one or two steps differ and the rest are identical → Template Method is leaner than Strategy."
Anti-patterns:
- Calling abstract methods in the constructor: The subclass hasn't finished initializing yet; this causes subtle bugs.
- Overriding the template method itself: Defeats the entire purpose — the algorithm structure is no longer guaranteed.
- Too many abstract steps: If every step is abstract, the base class provides no value. Consider if Strategy is more appropriate.
A team is building a data export service where three exporters (CSV, JSON, XML) all follow the same pipeline: open connection → fetch records → format records → write output → close connection. The formatting step differs per exporter, but the other steps are identical. A junior developer suggests using the Strategy pattern instead of Template Method because 'the exporters are different classes anyway.' Which argument best justifies choosing Template Method over Strategy here?
5. Real-World Usage
1. Java's AbstractList (java.util.AbstractList)
AbstractList defines the for iteration and bulk operations (addAll, removeAll, etc.). Concrete subclasses like ArrayList only implement get(int index) and size(). The shared iteration logic lives in AbstractList.
2. JUnit's test lifecycle
JUnit's test runner defines the : setUp() → runTest() → tearDown(). Your test class overrides setUp and tearDown as hooks. The runner guarantees teardown always runs even if the test fails — you can't accidentally break that guarantee by subclassing.
3. Python's http.server.BaseHTTPRequestHandler
The base class defines the request-handling pipeline: parse the request line, parse headers, then dispatch to do_GET(), do_POST(), etc. Subclasses override the do_* methods. The parsing and dispatch scaffolding is never duplicated.
In JUnit's test lifecycle, the framework guarantees that tearDown() always runs even if a test throws an exception. Which benefit of the Template Method pattern does this best illustrate?
6. Interview Cheat Sheet
Key sentences to say:
- "The pattern separates the what happens — defined in the base class — from the how each step works — defined in subclasses."
- "I mark the
finalin Java to prevent subclasses from reordering the algorithm steps — that invariance is the whole point." - "Hook methods are opt-in extension points: the base class provides a no-op default, and subclasses override only when needed."
- "Template Method uses to vary behavior; uses . If I need runtime swappability, I'd choose instead."
- "The risk is the fragile base class problem: a change to the base class algorithm affects every subclass. Keep the base class stable."
Common follow-up questions:
Q: What's the difference between Template Method and Strategy? A: Template Method uses — you subclass to customize. Strategy uses — you inject a strategy object. Template Method fixes the algorithm structure; Strategy allows the entire algorithm to be swapped. Use Template Method when the pipeline is stable and steps vary; use Strategy when the whole algorithm varies or needs runtime swapping.
Q: When would you make the template method final?
A: Always in Java, unless there's a deliberate reason not to. The template method's value is the guaranteed sequence. If a subclass can override it, there's no guarantee, and the pattern provides no benefit.
Q: What's a hook method?
A: A method in the abstract base class with a default (often empty) implementation. Subclasses may override it for optional behavior. For example, a beforeWrite() hook lets subclasses add logging or flushing without requiring every subclass to implement it.
Connections to other concepts:
- Method is a specialization: when the "step" being deferred is object creation, the template method's abstract step becomes a Method.
- Enables OCP: the base class algorithm is closed for modification; new behavior is added by creating new subclasses.
- Contrasts with Strategy: Template Method = inheritance-based variation; Strategy = composition-based variation.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.