The Template Method pattern — fixing the steps, varying the details

Strategy swaps out an entire algorithm at runtime. Template Method fixes the overall sequence of steps in a base class and lets subclasses override just the specific steps that actually vary — a different answer to a similar-sounding problem.

Intermediate

4 min read

The problem: several algorithms that share the same overall shape

class CSVReportGenerator:
    def generate(self, data):
        self.validate(data)
        formatted = self.format_data(data)
        return self.write_output(formatted, "csv")
 
class PDFReportGenerator:
    def generate(self, data):
        self.validate(data)         # identical to the CSV version
        formatted = self.format_data(data)   # identical to the CSV version
        return self.write_output(formatted, "pdf")   # only this line actually differs

Both report generators follow the exact same sequence — validate, format, write — and only the final step genuinely differs between them. Without a shared structure, validate and format_data's logic gets duplicated in every subclass that needs the same overall sequence, and any change to that shared sequence (a new required step, a reordered check) means finding and updating every duplicate independently.

The fix: the base class owns the sequence, subclasses override the steps

from abc import ABC, abstractmethod
 
class ReportGenerator(ABC):
    def generate(self, data):                 # the "template" — fixed sequence, never overridden
        self.validate(data)
        formatted = self.format_data(data)
        return self.write_output(formatted)
 
    def validate(self, data):                  # shared default — subclasses can use as-is
        if not data:
            raise ValueError("no data to report on")
 
    @abstractmethod
    def format_data(self, data): ...             # must be provided by each subclass
 
    @abstractmethod
    def write_output(self, formatted): ...        # must be provided by each subclass
 
class CSVReportGenerator(ReportGenerator):
    def format_data(self, data):
        return ",".join(str(v) for v in data)
    def write_output(self, formatted):
        return f"report.csv: {formatted}"
 
class PDFReportGenerator(ReportGenerator):
    def format_data(self, data):
        return " | ".join(str(v) for v in data)
    def write_output(self, formatted):
        return f"report.pdf: {formatted}"

generate() — the template method the pattern is named for — is defined exactly once, in the base class, and is never overridden. It calls validate, format_data, and write_output in a fixed order; subclasses only override the individual steps that actually need to differ (format_data, write_output), while inheriting validate's shared default behavior unchanged. The overall sequence lives in exactly one place, permanently, no matter how many report types get added.

Why this is a genuinely different shape from Strategy

# Strategy: the caller holds a strategy object and calls it — composition
class Order:
    def __init__(self, discount_strategy):
        self.discount_strategy = discount_strategy   # a separate object, injected in
    def final_total(self):
        return self.discount_strategy.apply(self.total)
 
# Template Method: the subclass IS the customized algorithm — inheritance
class CSVReportGenerator(ReportGenerator):   # inherits and overrides specific steps
    ...

Strategy (from its own lesson) swaps out an entire algorithm as an interchangeable object, held by composition — Order doesn't inherit from DiscountStrategy, it just holds one. Template Method fixes the algorithm's overall structure in a base class and uses inheritance — subclasses fill in specific steps of an algorithm they're literally a part of, not a wholly separate swappable object. This is the composition-over-inheritance lesson's trade-off showing up directly: Template Method deliberately chooses inheritance, accepting its costs (a fixed class hierarchy, can't swap steps at runtime) in exchange for guaranteeing every subclass follows the exact same sequence — a guarantee pure composition doesn't provide as directly.

The "hook" variant: optional steps subclasses can override, but don't have to

class ReportGenerator(ABC):
    def generate(self, data):
        self.validate(data)
        formatted = self.format_data(data)
        if self.should_include_summary():        # a hook — has a default, optional to override
            formatted += self.summary(formatted)
        return self.write_output(formatted)
 
    def should_include_summary(self):
        return False   # default: no summary, unless a subclass says otherwise

Not every step needs to be @abstractmethod (mandatory). A hook is a step with a sensible default that most subclasses can leave alone, but specific subclasses can override to customize just that one piece of behavior — should_include_summary defaults to False, and only a subclass that actually wants a summary needs to override it. This is the difference between "every subclass must provide this" (abstract methods) and "here's a reasonable default, override only if you need something different" (hooks) — both are legitimate parts of the same pattern, chosen per step based on whether a sensible default actually exists.

Where this shows up in code you already use

Django's TestCase (from the testing lesson) is a real Template Method: the test runner calls setUp(), then the test method itself, then tearDown(), in a fixed sequence it controls — your test class only overrides the specific pieces (setUp, the test method) rather than reimplementing the whole "run a test, isolated in a transaction, then clean up" sequence. Python's own unittest.TestCase works identically, and so does much of Django's class-based view dispatch (from its own lesson) — as_view() calling dispatch(), which calls get()/post(), is the same fixed-sequence-with-overridable-steps shape.

The concrete signal Template Method belongs somewhere

Several classes independently implementing the same overall sequence of steps, with only specific individual steps actually varying between them, is the tell — especially once that shared sequence needs to change and every implementation would need updating independently. If the "algorithms" being compared don't actually share a common sequence of steps at all — they're just different self-contained approaches — that's Strategy's shape instead, not Template Method's.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. In the ReportGenerator example, why does the base class's generate() method never get overridden by subclasses?

2. What's the structural difference between Template Method and Strategy?

3. What's the difference between an abstract step and a 'hook' in the Template Method pattern?

4. How is Django's TestCase (setUp -> test method -> tearDown) an example of Template Method?