What is it? #
The Template Method pattern defines the overall sequence of an operation in a base class, leaving specific steps for subclasses to implement.
The base class owns the order. Subclasses own the details. Nobody can accidentally run the steps in the wrong sequence.
It is the inheritance-based counterpart to Strategy. Where Strategy composes behaviour by passing an object in, Template Method uses subclassing and a fixed skeleton.
That makes it convenient and also more rigid. If several steps vary independently, composition scales better.
Think of it like this #
A recipe for baking. Preheat, mix, bake, cool, serve — the order never changes. What goes into the mixing bowl does.
Writing that order once means no variant forgets to preheat, and a new flavour only has to specify its ingredients.
Simple example #
Three data importers — CSV, JSON and API — all follow the same steps: fetch, parse, validate, transform, save, report. Only fetching and parsing differ.
Code #
from abc import ABC, abstractmethod
class DataImporter(ABC):
"""The base class owns the sequence; subclasses fill in the varying steps."""
def run(self, source: str) -> dict: # the template method
raw = self.fetch(source)
records = self.parse(raw)
valid, rejected = self._validate(records)
transformed = [self.transform(record) for record in valid]
saved = self._save(transformed)
self.after_import(saved, rejected) # hook: optional override
return {"saved": saved, "rejected": len(rejected)}
# --- steps every subclass must provide ---
@abstractmethod
def fetch(self, source: str) -> str: ...
@abstractmethod
def parse(self, raw: str) -> list[dict]: ...
# --- steps with a sensible shared default ---
def transform(self, record: dict) -> dict:
return {k.strip().lower(): v for k, v in record.items()}
def after_import(self, saved: int, rejected: list) -> None:
"""Hook: does nothing unless a subclass wants it."""
# --- shared steps subclasses cannot change ---
def _validate(self, records):
valid, rejected = [], []
for record in records:
(valid if record.get("id") else rejected).append(record)
return valid, rejected
def _save(self, records) -> int:
print(f"saving {len(records)} records")
return len(records)
class CsvImporter(DataImporter):
def fetch(self, source): return "id,name\n1,Ravi\n2,Anita"
def parse(self, raw):
lines = raw.splitlines()
header = lines[0].split(",")
return [dict(zip(header, line.split(","))) for line in lines[1:]]
class ApiImporter(DataImporter):
def fetch(self, source): return '[{"id": "7", "Name": " Sam "}]'
def parse(self, raw):
import json
return json.loads(raw)
def after_import(self, saved, rejected): # uses the hook
print(f"api import finished: {saved} saved, {len(rejected)} rejected")
print(CsvImporter().run("people.csv"))
print(ApiImporter().run("https://api.example.com/people"))
When to use it
- several variants share a fixed sequence of steps
- only one or two steps differ between them
- the order of steps must not vary
When NOT to use it
- several steps vary independently — use composition
- subclasses need to change the order
- the base class grows hooks nobody uses
How it works #
run is the template method. It defines the sequence and is not meant to be overridden — that is the guarantee the pattern provides.
fetch and parse are abstract. Every importer must supply them, and Python refuses to instantiate a subclass that does not.
transform has a shared default that subclasses may override. Most importers are happy with the default normalisation.
after_import is a hook: an empty method existing purely as an optional extension point. CsvImporter ignores it; ApiImporter uses it for reporting.
_validate and _save are shared and underscore-prefixed to signal they are internal. Their behaviour is identical everywhere, so subclasses have no business changing them.
Adding a fourth importer means writing two methods. The sequence, validation and saving come for free and cannot be got wrong.
The trade-off is inheritance's usual one. The subclass is tied to the base, and a change to the skeleton affects every importer. When more than one or two steps vary, injecting strategies gives more freedom.
Real-world use #
Test frameworks use this pattern: setUp, the test, tearDown — the framework owns the order and you fill in the parts.
Web framework class-based views do the same: a base handles the request lifecycle and you override the method for your verb.
Data pipelines, report generators, deployment scripts and ETL jobs all fit the shape: the steps are fixed, the sources differ.
The pattern is also common in libraries that want to let users extend behaviour at defined points without letting them rearrange the process. Hooks make the extension points explicit, which is friendlier than expecting users to know what to override.
Common mistakes #
- Overriding the template method itself, which discards the guaranteed sequence.
- Adding hooks nobody uses, cluttering the base class.
- Letting subclasses depend on base class internals beyond the defined steps.
- Using it when several steps vary independently — composition fits better.
- Making every step abstract, so subclasses must implement things they do not care about.
Practice #
Write a ReportGenerator base class with a fixed sequence: gather data, filter, format, deliver. Implement two subclasses — a daily sales report delivered to console and a monthly report delivered to a file. Add one optional hook and use it in only one subclass.