bigonotes
CS ConceptsDesign Patterns

Template Method Pattern

Fix an algorithm's sequence of steps in a base class and let subclasses fill in only the steps that vary.

6 min readLast updated Design Patterns

PDF, Excel and HTML reports all load data, format a header, body and footer, and save the file. Only the formatting differs. Copying the whole flow into each class duplicates the shared steps and lets the order drift. Template Method writes the sequence once in a base class and lets subclasses override only the steps that vary.

Intent#

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. (GoF)

Analogy: baking a cake always follows the same steps:

  1. Mix.
  2. Pour into the pan.
  3. Bake.
  4. Cool.
  5. Decorate.

A chocolate cake and a vanilla cake change the ingredients, never the order of the steps.

The problem: duplicated structure#

Without the pattern, each report class repeats the whole flow:

class PDFReport:
    def generate(self) -> None:
        self.load_data()      # same everywhere
        self.format_header()  # differs
        self.format_body()    # differs
        self.format_footer()  # differs
        self.save_to_file()   # same everywhere


class ExcelReport:
    def generate(self) -> None:
        self.load_data()            # duplicated
        self.format_excel_header()
        self.format_excel_body()
        self.format_excel_footer()
        self.save_to_file()         # duplicated
# The common steps AND the sequence are copied into every class

Structure#

The pattern splits an algorithm into a fixed skeleton and the steps that vary.

Part Role
Abstract class Holds the template method and the common steps
Template method Calls the steps in a fixed order; subclasses shouldn't override it (mark it @final)
Abstract steps (primitive operations) Steps every subclass must implement
Hooks Optional steps with a default (often empty) that subclasses may override
Concrete classes Implement the abstract steps and override hooks as needed

For the reports, Report.generate() is marked @final and runs this fixed sequence:

Step Method Implemented by
1 _load_data() Report (common)
2 format_header() Subclass (abstract)
3 format_body() Subclass (abstract)
4 format_footer() Subclass (abstract)
5 _save_to_file() Report (common)

This is the Hollywood Principle: "Don't call us, we'll call you." The base class calls into subclass code, not the other way round. That's how frameworks give you extension points.

Example 1: report generation#

The base class owns the sequence; each report fills in the formatting.

from abc import ABC, abstractmethod
from typing import final


class Report(ABC):
    # The template method: @final tells type checkers not to let
    # subclasses override it, so the order can't be changed
    @final
    def generate(self) -> None:
        self._load_data()
        self.format_header()
        self.format_body()
        self.format_footer()
        if self.should_compress():  # hook: optional step
            self._compress()
        self._save_to_file()

    # Common steps, written once
    def _load_data(self) -> None:
        print("Loading data from database...")

    def _compress(self) -> None:
        print("Compressing...")

    def _save_to_file(self) -> None:
        print("Saving report to file...")

    # Abstract steps: every report must supply these
    @abstractmethod
    def format_header(self) -> None: ...

    @abstractmethod
    def format_body(self) -> None: ...

    @abstractmethod
    def format_footer(self) -> None: ...

    # Hook: sensible default, subclasses may override
    def should_compress(self) -> bool:
        return False


class PDFReport(Report):
    def format_header(self) -> None:
        print("PDF header with logo")

    def format_body(self) -> None:
        print("PDF body with tables and charts")

    def format_footer(self) -> None:
        print("PDF footer with page numbers")


class ExcelReport(Report):
    def format_header(self) -> None:
        print("Excel header row")

    def format_body(self) -> None:
        print("Excel spreadsheet data")

    def format_footer(self) -> None:
        print("Excel totals row")

    def should_compress(self) -> bool:
        return True  # opts in to the hook


class HTMLReport(Report):
    def format_header(self) -> None:
        print("HTML header with CSS")

    def format_body(self) -> None:
        print("HTML body with divs")

    def format_footer(self) -> None:
        print("HTML footer with links")


PDFReport().generate()
ExcelReport().generate()

Output:

Loading data from database...
PDF header with logo
PDF body with tables and charts
PDF footer with page numbers
Saving report to file...
Loading data from database...
Excel header row
Excel spreadsheet data
Excel totals row
Compressing...
Saving report to file...

Example 2: data import pipeline#

@final is only a hint for type checkers; at runtime nothing stops an override. So it also helps to document that subclasses shouldn't override the template method.

from abc import ABC, abstractmethod


class DataImporter(ABC):
    def run(self, path: str) -> int:
        """Template method: read, parse, validate, store.

        Don't override.
        """
        raw = self.read(path)
        records = self.parse(raw)
        valid = [r for r in records if self.is_valid(r)]
        self.store(valid)
        return len(valid)

    def read(self, path: str) -> str:  # common step
        with open(path, encoding="utf-8") as f:
            return f.read()

    @abstractmethod
    def parse(self, raw: str) -> list[dict]: ...  # varies per format

    def is_valid(self, record: dict) -> bool:  # hook with a default
        return True

    def store(self, records: list[dict]) -> None:  # common step
        print(f"Stored {len(records)} records")


class CsvImporter(DataImporter):
    def parse(self, raw: str) -> list[dict]:
        lines = raw.strip().splitlines()
        header, *rows = [line.split(",") for line in lines]
        return [dict(zip(header, row)) for row in rows]

    def is_valid(self, record: dict) -> bool:
        return bool(record.get("id"))  # skip rows without an id

Real-world examples#

Frameworks use Template Method to give you extension points:

  • unittest.TestCase: the framework runs setUp(), then the test method, then tearDown(); you override the pieces.
  • http.server.BaseHTTPRequestHandler dispatches each request to do_GET(), do_POST() and friends, which you write.
  • collections.abc.Sequence: implement __getitem__ and __len__ and inherit __contains__, __iter__, index() and count(), built on them.
  • threading.Thread: start() sets up the thread and calls your run().
  • Context managers (with): a fixed sequence (acquire, run your block, clean up even on errors) with your code in the middle. They use composition rather than subclassing, the cousin of the same idea.

Domain flows fit the same mould:

Flow Fixed steps, in order
Game loop Input, update, render
Build pipeline Compile, test, package
Loan approval Apply, verify (varies), decide
Authentication Receive credentials, validate, issue token

When to use it#

Template Method fits when:

  • Several classes share the same sequence of steps and differ only in some of them.
  • You see the same algorithm skeleton duplicated across classes.
  • You're designing a framework and want controlled extension points.
  • The order of steps is an invariant that must not change.

When not to use it#

Skip it when:

  • The flow itself must vary, or be chosen at runtime. Use Strategy.
  • You'd rather avoid inheritance (deep hierarchies, or the class already extends something else).
  • There's only one implementation, or the variants share no steps.

Pitfalls#

Most problems come from leaning on inheritance.

  • Inheritance coupling. Subclasses depend on the base class's internals; changing the skeleton can break every subclass.
  • Too many abstract steps. Every subclass must implement them all. Prefer hooks with defaults for optional steps.
  • Forgetting @final. If the template method is overridden, a subclass can reorder or skip steps and break the invariant.
  • Hard-to-follow flow. Control jumps between parent and child. Keep the template method short and well named.
  • Liskov violations. Subclasses that override a step in a surprising way (throwing, skipping side effects) break callers' expectations.

Template Method vs similar patterns#

Compare it with the patterns it's most often confused with:

Pattern Difference
Strategy Strategy swaps the whole algorithm via composition, even at runtime; Template Method varies some steps via inheritance, fixed at compile time
Factory Method A specialization: the step that varies is "create the object". Template methods often call factory methods
Builder In a fluent builder the client controls the steps; in Template Method the parent class does
State State changes behavior dynamically as state changes; Template Method keeps one fixed flow

Key takeaways#

  • The base class owns the sequence (the template method, marked @final); subclasses own the details.
  • Abstract steps are required; hooks are optional overrides with defaults.
  • "Don't call us, we'll call you": the parent calls the child's steps, which is how frameworks offer extension points.
  • If the whole algorithm needs to vary at runtime, use Strategy instead.

Found a mistake? Tell me on LinkedIn ↗

Further reading#