Template Method Pattern

8 min read

Reading Progress0%
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
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.


QUICK CHECK

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?

Choose one answer

2. How It Works

  1. An abstract base class defines the — a regular (often final) method that calls a sequence of steps.
  2. Some steps are abstract methods — subclasses must implement them.
  3. Other steps are hook methods — they have default (often empty) implementations that subclasses may optionally override.
  4. 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");
    }
}

QUICK CHECK

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?

Choose one answer

3. Variants & Comparisons

vs. :

ApproachHow It WorksProsConsBest For
Template MethodInheritance — subclass overrides stepsAlgorithm structure is enforced; low overheadRequires subclassing; harder to swap at runtimeFixed pipeline with variant steps
StrategyComposition — delegate algorithm to injected objectSwappable at runtime; no inheritance neededMore classes; client must know which strategyFully interchangeable algorithms
Hook MethodsOptional override in base classFlexible extension points without forcing overridesHooks can be missed or misusedOptional 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.


QUICK CHECK

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?

Choose one answer

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.

QUICK CHECK

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?

Choose one answer

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.


QUICK CHECK

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?

Choose one answer

6. Interview Cheat Sheet

Key sentences to say:

  1. "The pattern separates the what happens — defined in the base class — from the how each step works — defined in subclasses."
  2. "I mark the final in Java to prevent subclasses from reordering the algorithm steps — that invariance is the whole point."
  3. "Hook methods are opt-in extension points: the base class provides a no-op default, and subclasses override only when needed."
  4. "Template Method uses to vary behavior; uses . If I need runtime swappability, I'd choose instead."
  5. "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.