Template Method
Defines the skeleton of an algorithm, deferring some steps to subclasses.
Template Method Pattern: A Complete Guide with Examples in 11 Programming Languages
The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class, deferring certain steps to subclasses. The base class declares the algorithm's structure — the sequence of steps — and provides default implementations for the steps that are shared across all variants. Subclasses override only the steps that differ, without changing the algorithm's overall structure. The base class calls the steps; the subclasses fill them in.
In this comprehensive guide, we'll explore the Template Method Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and when to use this powerful pattern.
Table of Contents
- What is the Template Method Pattern?
- Why Use the Template Method Pattern?
- Template Method Pattern Comparison
- Template Method Pattern Explained
- Class Diagram
- Beginner-Friendly Example
- Production-Ready Example
- Real-World Use Cases
- Language-Specific Mistakes and Anti-Patterns
- Frequently Asked Questions
- Key Takeaways
What is the Template Method Pattern?
The Template Method Pattern addresses a very specific problem: you have several classes that perform the same overall algorithm — the same sequence of steps — but each class performs one or more of those steps differently. The naive approach duplicates the algorithm skeleton in every class, leading to code that diverges over time: a bug fix in one class's algorithm ordering must be manually replicated in every other class.
The Template Method Pattern solves this by inverting control. Instead of each subclass owning its own algorithm, the base class owns the algorithm structure and calls the subclass's overrides at the right points. The base class is in charge of when each step runs. The subclasses are in charge of how their specific steps run.
Think of it like a franchise restaurant. Every franchise follows the same operational procedure: open the store, prepare ingredients, take orders, cook food, serve customers, close the store. The franchise agreement (base class) defines this sequence. Individual franchise locations (subclasses) customize specific steps — their local suppliers, regional menu items, local marketing — without changing the overall daily procedure. The franchisor ensures every location runs the same proven process.
Why Use the Template Method Pattern?
The Template Method Pattern offers several compelling benefits:
- Eliminates Code Duplication: The algorithm skeleton lives in exactly one place — the base class. All subclasses inherit the same structure automatically, and changes to the structure propagate to every subclass at once
- Hollywood Principle ("Don't Call Us, We'll Call You"): The base class calls the subclass methods, not the other way around — the framework is in control; the subclass just fills in the blanks
- Open/Closed Principle: Add new algorithm variants by adding new subclasses — the base class template never changes
- Enforced Algorithm Invariants: Steps that must always run in a fixed order, or that must always execute, live in the base class and cannot be accidentally skipped or reordered by subclasses
- Hook Methods for Optional Extension: Subclasses can opt into additional behavior via hooks — empty base implementations that subclasses override only when needed
- Readable Intent: The template method in the base class reads like a high-level summary of the algorithm — it documents the process without burying it in implementation details
Template Method Pattern Comparison
Let's compare the Template Method Pattern with related patterns:
| Pattern | Mechanism | Who Varies Behavior | Coupling | Use Case |
|---|---|---|---|---|
| Template Method | Inheritance | Subclass overrides steps | High (base/sub relationship) | Algorithm skeleton with variable steps |
| Strategy | Composition | Client injects strategy object | Low (interface-based) | Interchangeable algorithms at runtime |
| Factory Method | Inheritance | Subclass creates the object | High (base/sub relationship) | Object creation with variable product type |
| Hook Method | Inheritance | Subclass opts into extension | High (base/sub relationship) | Optional extensions to a fixed process |
| Builder | Composition | Builder creates step-by-step | Low (interface-based) | Complex object construction |
Key Distinctions:
- Template Method vs. Strategy: This is the most important comparison. Both vary part of an algorithm. Template Method uses inheritance — the variable steps are abstract methods overridden by subclasses. Strategy uses composition — the variable algorithm is an injected object. Template Method is simpler when variants are known at compile time and the algorithm structure is fixed; Strategy is more flexible when the algorithm needs to change at runtime or be composed from independent parts. The Template Method pattern is "Prefer inheritance"; the Strategy pattern is "Prefer composition." In modern design, Strategy is often preferred for its looser coupling.
- Template Method vs. Factory Method: Factory Method IS a specialization of Template Method applied specifically to object creation. The "step" being overridden is the creation of a product object. A class with a Factory Method is implementing a Template Method where one of the steps is "create the product."
- Template Method vs. Hook Method: Hook methods are part of the Template Method Pattern, not a separate pattern. A hook is a concrete (non-abstract) method in the base class with an empty or default implementation that subclasses can optionally override. Abstract methods are mandatory to override; hooks are optional.
Template Method Pattern Explained
The Template Method Pattern involves two participants:
Core Components:
-
Abstract Class (Base): Contains the template method — a concrete method that defines the algorithm's skeleton by calling other methods in a fixed sequence. Also declares:
- Abstract primitive operations: Methods with no implementation that subclasses must override — the mandatory variable steps
- Concrete operations: Fully implemented methods shared by all subclasses — the invariant steps
- Hook methods: Concrete methods with empty or default implementations that subclasses may override — the optional extension points
-
Concrete Classes (Subclasses): Implement the abstract primitive operations, providing the specific behavior for their variant of the algorithm. They may also override hooks if they need the optional behavior. Concrete classes never override the template method itself — that would defeat the purpose of the pattern.
Template Method Structure:
templateMethod() { // ← Fixed — never overridden
step1() // ← Concrete (shared by all subclasses)
step2() // ← Abstract (each subclass must implement)
hook() // ← Hook (optional — subclasses can override)
step3() // ← Abstract (each subclass must implement)
step4() // ← Concrete (shared by all subclasses)
}
The Hollywood Principle:
The Template Method Pattern is the classic illustration of the Hollywood Principle: "Don't call us, we'll call you." The base class calls the subclass methods — not the other way around. The subclass doesn't orchestrate anything; it just provides the implementation for the steps the base class asks for. This inversion of control is the defining characteristic of frameworks and abstract classes everywhere.
Variants:
- Minimal Template Method: The template method calls only abstract methods — maximum variation, minimal sharing
- Mostly Concrete Template: The template method calls mostly concrete methods with one or two abstract hooks — maximum sharing, minimal variation
- Hook-Heavy Template: Many optional hook points that subclasses can selectively override to customize behavior at multiple points without the obligation of implementing everything
- Final Template Method: The template method is marked
final(Java/Kotlin),sealed(C#), or documented as "do not override" — prevents subclasses from accidentally breaking the algorithm structure
Class Diagram
Here's the UML class diagram showing the Template Method Pattern structure:
Beginner-Friendly Example
Let's start with the most intuitive Template Method example: a data report generator that reads data, processes it, and renders the output. The base class defines the sequence — read, process, render. The subclasses vary how they read (CSV vs. JSON vs. database) and how they render (plain text vs. HTML vs. PDF-style). The processing and overall sequence are shared.
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List, Dict
# ── Abstract Class ─────────────────────────────────────────────────
class ReportGenerator(ABC):
"""
Template Method Pattern: defines the skeleton of report generation.
Subclasses implement the variable steps without changing the sequence.
"""
# ── Template Method ── (the algorithm skeleton — never override this)
def generate(self, title: str) -> str:
"""Generate a complete report. This is the template method."""
print(f" [ReportGenerator] Starting report: {title}")
data = self.read_data() # Abstract — subclass provides data source
records = self.process_data(data) # Abstract — subclass defines processing
self.before_render() # Hook — subclass may customize pre-render
output = self.render(title, records) # Abstract — subclass defines output format
self.after_render(output) # Hook — subclass may customize post-render
print(f" [ReportGenerator] Report complete ({len(records)} records)")
return output
# ── Abstract primitive operations ── (MUST be overridden)
@abstractmethod
def read_data(self) -> List[Dict]:
"""Read raw data from the source."""
@abstractmethod
def process_data(self, data: List[Dict]) -> List[Dict]:
"""Filter, sort, or transform the raw data."""
@abstractmethod
def render(self, title: str, records: List[Dict]) -> str:
"""Render the processed records into a formatted report string."""
# ── Concrete operations ── (shared by all subclasses)
def validate_records(self, records: List[Dict]) -> List[Dict]:
"""Remove records with missing required fields (shared logic)."""
return [r for r in records if r.get("name") and r.get("value") is not None]
# ── Hook methods ── (MAY be overridden — empty by default)
def before_render(self) -> None:
"""Called before rendering. Override for setup (e.g., open file handle)."""
pass
def after_render(self, output: str) -> None:
"""Called after rendering. Override for cleanup (e.g., write to disk)."""
pass
# ── Concrete Classes ────────────────────────────────────────────────
class CsvTextReport(ReportGenerator):
"""Reads CSV-style data, sorts by value, renders as plain text."""
def read_data(self) -> List[Dict]:
# Simulate reading a CSV file
raw = "Alice,320\nBob,150\nCarol,480\nDave,\nEve,290"
records = []
for line in raw.strip().splitlines():
parts = line.split(",")
records.append({
"name": parts[0].strip(),
"value": int(parts[1].strip()) if parts[1].strip().isdigit() else None,
})
print(f" [CSV] Read {len(records)} raw rows")
return records
def process_data(self, data: List[Dict]) -> List[Dict]:
valid = self.validate_records(data) # Uses shared concrete method
return sorted(valid, key=lambda r: r["value"], reverse=True)
def render(self, title: str, records: List[Dict]) -> str:
lines = [f"=== {title} ===", f"{'Name':<12} {'Value':>8}", "-" * 22]
for r in records:
lines.append(f"{r['name']:<12} {r['value']:>8}")
lines.append(f"\nTotal: {sum(r['value'] for r in records)}")
return "\n".join(lines)
class JsonHtmlReport(ReportGenerator):
"""Reads JSON-style data, filters below threshold, renders as HTML."""
def __init__(self, threshold: int = 200) -> None:
self._threshold = threshold
self._output_path: str | None = None
def read_data(self) -> List[Dict]:
# Simulate reading JSON data
import json
raw = '[{"name":"Alice","value":320},{"name":"Bob","value":150},{"name":"Carol","value":480},{"name":"Dave"},{"name":"Eve","value":290}]'
records = json.loads(raw)
print(f" [JSON] Read {len(records)} raw records")
return records
def process_data(self, data: List[Dict]) -> List[Dict]:
valid = self.validate_records(data)
# Filter: only include records above threshold
return [r for r in valid if r["value"] >= self._threshold]
def before_render(self) -> None:
print(f" [HTML] Applying threshold filter: ≥{self._threshold}")
def render(self, title: str, records: List[Dict]) -> str:
rows = "\n".join(
f" <tr><td>{r['name']}</td><td>{r['value']}</td></tr>"
for r in records
)
return (
f"<html><body>\n"
f" <h1>{title}</h1>\n"
f" <table>\n <tr><th>Name</th><th>Value</th></tr>\n{rows}\n </table>\n"
f"</body></html>"
)
def after_render(self, output: str) -> None:
print(f" [HTML] Report size: {len(output)} characters")
# ── Client ─────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== Data Report Generator ===\n")
print("--- CSV Text Report ---")
csv_report = CsvTextReport()
output = csv_report.generate("Q4 Sales Report")
print("\n" + output)
print("\n--- JSON HTML Report (threshold=200) ---")
html_report = JsonHtmlReport(threshold=200)
output = html_report.generate("Q4 Sales Report")
print("\n" + output)
Production-Ready Example
Now let's build a data pipeline processor — a real-world application of the Template Method Pattern. Data pipelines across all systems share the same fundamental steps: connect to the source, extract the data, transform and validate it, load it into the destination, and generate a summary. Each pipeline variant customizes the source and destination, while the orchestration and error handling remain in the base class.
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from datetime import datetime
import time
# ── Pipeline Result ────────────────────────────────────────────────
@dataclass
class PipelineResult:
pipeline_name: str
started_at: str
completed_at: str
records_read: int
records_valid: int
records_skipped: int
records_loaded: int
errors: List[str] = field(default_factory=list)
success: bool = True
def display(self) -> None:
status = "✅ SUCCESS" if self.success else "❌ FAILED"
print(f"\n Pipeline: {self.pipeline_name}")
print(f" Status : {status}")
print(f" Started : {self.started_at} → Completed: {self.completed_at}")
print(f" Records : Read={self.records_read} | Valid={self.records_valid} | Skipped={self.records_skipped} | Loaded={self.records_loaded}")
if self.errors:
print(f" Errors : {'; '.join(self.errors)}")
# ── Abstract Base Pipeline ─────────────────────────────────────────
class DataPipeline(ABC):
"""Template Method Pattern: the ETL pipeline skeleton."""
def __init__(self, name: str) -> None:
self._name = name
self._result: Optional[PipelineResult] = None
# ── Template Method ── (the full ETL algorithm — never override)
def run(self) -> PipelineResult:
started = datetime.now().strftime("%H:%M:%S")
print(f"\n ╔══ Pipeline: {self._name} ══")
try:
# 1. Connect
print(" ║ [1] Connecting to source...")
self.connect()
# 2. Extract
print(" ║ [2] Extracting data...")
raw_data = self.extract()
print(f" ║ Extracted {len(raw_data)} raw records")
# 3. Transform & Validate (shared logic + subclass customization)
print(" ║ [3] Transforming and validating...")
transformed = self.transform(raw_data)
validated, skipped, errors = self.validate(transformed)
print(f" ║ Valid={len(validated)}, Skipped={skipped}, Errors={len(errors)}")
# 4. Pre-load hook
self.before_load(validated)
# 5. Load
print(" ║ [4] Loading into destination...")
loaded = self.load(validated)
print(f" ║ Loaded {loaded} records")
# 6. Post-load hook
self.after_load(loaded)
completed = datetime.now().strftime("%H:%M:%S")
self._result = PipelineResult(
pipeline_name=self._name, started_at=started, completed_at=completed,
records_read=len(raw_data), records_valid=len(validated),
records_skipped=skipped, records_loaded=loaded, errors=errors,
)
except Exception as e:
completed = datetime.now().strftime("%H:%M:%S")
print(f" ║ ❌ Pipeline failed: {e}")
self._result = PipelineResult(
pipeline_name=self._name, started_at=started, completed_at=completed,
records_read=0, records_valid=0, records_skipped=0, records_loaded=0,
errors=[str(e)], success=False,
)
finally:
self.disconnect()
print(" ╚══ Done")
return self._result
# ── Abstract primitive operations ──
@abstractmethod
def connect(self) -> None:
"""Establish connection to the data source."""
@abstractmethod
def extract(self) -> List[Dict[str, Any]]:
"""Extract raw data from the source."""
@abstractmethod
def transform(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Transform raw records into the target schema."""
@abstractmethod
def load(self, records: List[Dict[str, Any]]) -> int:
"""Load validated records into the destination. Returns count loaded."""
# ── Concrete shared operations ── (invariant across all pipelines)
def validate(self, records: List[Dict[str, Any]]) -> tuple[List[Dict[str, Any]], int, List[str]]:
"""Shared validation: check required fields and data types."""
valid, skipped, errors = [], 0, []
required = self.required_fields()
for i, record in enumerate(records):
missing = [f for f in required if not record.get(f)]
if missing:
errors.append(f"Record {i}: missing {missing}")
skipped += 1
else:
valid.append(record)
return valid, skipped, errors
def required_fields(self) -> List[str]:
"""Default required fields — subclasses may override."""
return ["id", "name"]
def disconnect(self) -> None:
"""Clean up connection resources."""
print(" ║ [5] Disconnecting from source")
# ── Hook methods ── (optional extension points)
def before_load(self, records: List[Dict[str, Any]]) -> None:
"""Called before loading. Override to add pre-load setup (e.g., truncate table)."""
pass
def after_load(self, count: int) -> None:
"""Called after loading. Override for notifications or downstream triggers."""
pass
# ── Concrete Pipelines ─────────────────────────────────────────────
class CustomerCsvPipeline(DataPipeline):
"""Reads customer data from a CSV source, loads into a CRM destination."""
def __init__(self, filepath: str, crm_endpoint: str) -> None:
super().__init__("CustomerCSV → CRM")
self._filepath = filepath
self._crm_endpoint = crm_endpoint
self._destination_records: List[Dict] = []
def connect(self) -> None:
# Simulate file open and CRM API auth
print(f" ║ CSV: {self._filepath} | CRM: {self._crm_endpoint}")
def extract(self) -> List[Dict[str, Any]]:
# Simulate reading 5 CSV rows
return [
{"id": "C001", "name": "Alice Smith", "email": "alice@example.com", "tier": "gold"},
{"id": "C002", "name": "Bob Jones", "email": "bob@example.com", "tier": "silver"},
{"id": "C003", "name": "", "email": "noname@example.com","tier": "bronze"}, # Invalid: no name
{"id": "C004", "name": "Carol Williams","email": "carol@example.com", "tier": "gold"},
{"id": "", "name": "Dave Brown", "email": "dave@example.com", "tier": "silver"}, # Invalid: no id
]
def transform(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
# Normalize: uppercase tier, strip whitespace, add import timestamp
transformed = []
for r in data:
transformed.append({
"id": r["id"].strip(),
"name": r["name"].strip(),
"email": r["email"].strip().lower(),
"tier": r.get("tier", "bronze").upper(),
"imported_at": datetime.now().isoformat(),
})
return transformed
def required_fields(self) -> List[str]:
return ["id", "name", "email"] # Override: email also required for customers
def load(self, records: List[Dict[str, Any]]) -> int:
# Simulate CRM API POST
self._destination_records = records
time.sleep(0.05) # Simulate network latency
print(f" ║ CRM upsert: {len(records)} customers")
return len(records)
def after_load(self, count: int) -> None:
print(f" ║ [Hook] Triggering CRM welcome emails for {count} new customers")
class OrderApiPipeline(DataPipeline):
"""Reads orders from a REST API, loads into a data warehouse."""
def __init__(self, api_url: str, warehouse_table: str) -> None:
super().__init__("OrderAPI → DataWarehouse")
self._api_url = api_url
self._warehouse_table = warehouse_table
def connect(self) -> None:
print(f" ║ API: {self._api_url} | DW table: {self._warehouse_table}")
def extract(self) -> List[Dict[str, Any]]:
# Simulate paginated API response
return [
{"id": "O100", "name": "Order #100", "customer_id": "C001", "amount": 299.99, "status": "shipped"},
{"id": "O101", "name": "Order #101", "customer_id": "C002", "amount": 49.99, "status": "pending"},
{"id": "O102", "name": "Order #102", "customer_id": "", "amount": 149.00, "status": "delivered"}, # Invalid
{"id": "O103", "name": "Order #103", "customer_id": "C004", "amount": 599.00, "status": "shipped"},
]
def transform(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
return [{
"id": r["id"],
"name": r["name"],
"customer_id": r.get("customer_id", "").strip(),
"amount_usd": round(float(r.get("amount", 0)), 2),
"status": r.get("status", "unknown").lower(),
"loaded_at": datetime.now().isoformat(),
} for r in data]
def required_fields(self) -> List[str]:
return ["id", "name", "customer_id"]
def before_load(self, records: List[Dict[str, Any]]) -> None:
print(f" ║ [Hook] Staging {len(records)} records for batch insert")
def load(self, records: List[Dict[str, Any]]) -> int:
# Simulate warehouse batch insert
time.sleep(0.05)
print(f" ║ Warehouse INSERT INTO {self._warehouse_table}: {len(records)} rows")
return len(records)
def after_load(self, count: int) -> None:
total_amount = 0.0 # In real code, compute from loaded records
print(f" ║ [Hook] Refreshing revenue materialized view")
# ── Client ─────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== ETL Data Pipeline Processor ===")
customer_pipeline = CustomerCsvPipeline(
filepath="customers.csv",
crm_endpoint="https://crm.example.com/api/v2"
)
result1 = customer_pipeline.run()
result1.display()
order_pipeline = OrderApiPipeline(
api_url="https://api.orders.example.com/v1/orders",
warehouse_table="dw.fact_orders"
)
result2 = order_pipeline.run()
result2.display()
Real-World Use Cases
1. ETL (Extract, Transform, Load) Data Pipelines
Every ETL pipeline follows the same phases: connect, extract, transform, validate, load, and clean up. The base pipeline class defines this sequence with error handling, logging, and metric collection baked in. Concrete pipeline classes provide the source-specific extract() (read from S3, Kafka, PostgreSQL, REST API) and destination-specific load() (write to a data warehouse, push to another API, insert into a queue). The orchestration never changes; only the connectors do.
2. Test Frameworks (JUnit, pytest, unittest)
Every test runner uses the Template Method Pattern. The setUp() → test*() → tearDown() sequence is a template method — the framework calls your hook methods in a fixed order. JUnit's @Before, @Test, and @After annotations mark which methods fill which template slots. You never control when these are called; the framework does. You just fill in the blanks.
3. Web Request Handling (Middleware Pipelines)
HTTP frameworks like Django, Rails, Spring MVC, and ASP.NET define a template for request handling: parse request → authenticate → authorize → route → execute view → render response → send. The framework owns the sequence; developers override only the steps they need. Django's class-based views (get(), post(), form_valid()) are hooks into its request-handling template method.
4. Serialization and Parsing
File parsers for structured formats (XML, JSON, YAML, CSV, Protobuf) share the same template: open stream → read header → parse records → read footer → close stream. A base FileParser defines this sequence. XML, JSON, and CSV subclasses provide their own parseHeader(), parseRecord(), and parseFooter() implementations. The stream management, error handling, and encoding detection are shared.
5. Build Systems (Make, Maven, Gradle, CMake)
Build systems define a lifecycle with fixed phases: validate → initialize → compile → test → package → verify → install → deploy. Each phase is a hook. Build plugins and lifecycle bindings attach to these hooks to run tasks. The build order is the template method — the build system calls the hooks in the right sequence; plugins fill in the behavior.
6. Game AI Behavior Trees
Game AI characters share a behavioral loop: perceive environment → update state → select action → execute action → react to result. Each character type (soldier, civilian, boss) provides its own implementations of selectAction() and executeAction(). The perception and state update are shared base-class logic. The template method is the game loop tick.
7. Report Generation Systems
Enterprise reporting tools (Crystal Reports, JasperReports, SSRS) use the Template Method for report generation: open data source → execute query → format header → iterate and format rows → format footer → close and export. Each report template defines the query and formatting; the framework executes the sequence. The export format (PDF, Excel, HTML, CSV) is often a separate Strategy that the template method calls at the render step.
Language-Specific Mistakes and Anti-Patterns
❌ Mistake #1: Not Using ABC and @abstractmethod — Forgettable Step Implementations
# BAD: No ABC — subclasses can forget to implement abstract steps silently
class ReportGenerator:
def generate(self, title: str) -> str:
data = self.read_data() # If not overridden, raises AttributeError at runtime
records = self.process_data(data)
return self.render(title, records)
def read_data(self):
pass # Silent no-op! Subclass forgets to override → empty data, silent bug
class BadReport(ReportGenerator):
pass # Forgot to implement read_data — Python allows this silently
report = BadReport()
report.generate("Title") # No error at construction time — bug is hidden!
# GOOD: ABC enforces all abstract methods at instantiation time
from abc import ABC, abstractmethod
class ReportGenerator(ABC):
def generate(self, title: str) -> str:
data = self.read_data()
records = self.process_data(data)
return self.render(title, records)
@abstractmethod def read_data(self) -> list: ...
@abstractmethod def process_data(self, data: list) -> list: ...
@abstractmethod def render(self, title: str, records: list) -> str: ...
# class BadReport(ReportGenerator): pass
# BadReport() → TypeError: Can't instantiate abstract class with abstract methods
❌ Mistake #2: Allowing Subclasses to Override the Template Method
# BAD: Template method is not protected from override
class ReportGenerator:
def generate(self, title: str) -> str: # Nothing stops subclasses from overriding this
data = self.read_data()
records = self.process_data(data)
return self.render(title, records)
class BadReport(ReportGenerator):
def generate(self, title: str) -> str: # Completely bypasses the template!
return f"<h1>{title}</h1>" # Skips read_data, process_data, validate...
# All the shared logic (logging, error handling, metrics) is lost
# GOOD: Document the template method clearly and use naming conventions
class ReportGenerator(ABC):
def generate(self, title: str) -> str:
"""Template method — DO NOT OVERRIDE. Override read_data, process_data, render."""
# ... (Python has no final/sealed — use documentation + convention)
pass
❌ Mistake #3: Putting Too Much Logic in the Template Method
# BAD: Template method does too much — it's also a data processor
class ReportGenerator(ABC):
def generate(self, title: str) -> str:
data = self.read_data()
# All this logic belongs in a concrete operation, not the template method
validated = [r for r in data if r.get("name") and r.get("value") is not None]
sorted_data = sorted(validated, key=lambda r: r["value"], reverse=True)
total = sum(r["value"] for r in sorted_data)
return self.render(title, sorted_data, total)
# The template method now assumes sorting and totaling — not flexible at all!
# GOOD: Template method is high-level only; delegate steps to well-named methods
class ReportGenerator(ABC):
def generate(self, title: str) -> str:
data = self.read_data() # Source step
valid = self.validate(data) # Shared concrete step
records = self.process_data(valid) # Subclass step
self.before_render() # Hook
return self.render(title, records) # Subclass step
Frequently Asked Questions
Q1: What is the difference between the Template Method Pattern and the Strategy Pattern?
This is the most important distinction to understand. Both patterns vary part of an algorithm. The mechanism and trade-offs are opposite:
Template Method uses inheritance: The variable parts are abstract methods that subclasses override. The base class owns the skeleton; subclasses provide the implementations. Relationships between the skeleton and the steps are defined at compile time. Adding a new variant means creating a new subclass.
Strategy uses composition: The variable algorithm is an object injected into the context. The context delegates to the strategy interface. The algorithm can change at runtime. Adding a new variant means creating a new class that implements the strategy interface, with no subclassing.
The practical trade-off: Template Method is simpler when the number of variants is small, known at compile time, and the algorithm skeleton is stable. Strategy is more flexible when variants change at runtime, when you want to combine algorithms, or when you want to avoid deep inheritance hierarchies. Modern design advice generally prefers Strategy (composition) over Template Method (inheritance), captured by the principle "favor composition over inheritance."
Q2: What is the difference between abstract methods and hook methods?
Abstract methods are the mandatory variable steps. The base class declares them without an implementation; every concrete subclass must provide one. If a subclass doesn't implement an abstract method, it cannot be instantiated (in statically typed languages). They represent the parts of the algorithm that fundamentally differ between variants.
Hook methods are optional extension points. The base class provides a default implementation — typically empty — that subclasses can override if they need additional behavior at that point. A hook method that does nothing by default is not mandatory; subclasses opt in by overriding. They represent points where subclasses can insert behavior without it being required.
A good way to think about it: abstract methods are the template's "fill in the blank" slots; hooks are the template's "add something here if you want to" slots.
Q3: Should the template method be marked final?
Yes, whenever possible. Marking the template method final (Java/Kotlin), sealed (C#), or documenting it as "do not override" (Python/JavaScript) enforces the pattern's intent. The whole point of the Template Method Pattern is that the algorithm structure is invariant — the skeleton belongs to the base class and should not be changed by subclasses.
Languages that support it:
- Java/Kotlin:
finalon the method - C#:
sealed override(or just the method in a non-virtual-by-default context) - PHP:
finalkeyword on the method - Swift:
finalon the method in a class - Python/JavaScript: No language enforcement — use documentation and code review
Q4: Can the Template Method Pattern work without inheritance?
In most languages, Template Method is implemented with inheritance. However, the pattern's intent — defining an algorithm skeleton with variable steps — can be achieved with composition in languages that lack or discourage inheritance.
Go (no inheritance): Use an interface for the variable steps and a standalone template function that calls the interface methods. The "template method" is a free function, not a method on a type.
Rust (traits with defaults): Trait methods with default implementations serve as the template; required methods serve as the abstract steps. A free function version can enforce the skeleton even more strongly.
Functional languages: The template method is a higher-order function that takes function arguments for the variable steps — essentially a parameterized pipeline.
Q5: What is the Hollywood Principle, and how does it relate to the Template Method Pattern?
The Hollywood Principle states: "Don't call us, we'll call you." It describes inversion of control — instead of the lower-level code calling the higher-level code, the higher-level code calls the lower-level code.
In the Template Method Pattern: the base class (high-level) calls the subclass methods (lower-level). The subclass never calls the base class's algorithm; it just provides implementations for the steps it's asked to fill in. The framework is in control; the subclass is just a participant.
This is the same principle behind every framework ever built: the framework calls your code at the right points. Django calls your get(). JUnit calls your @Test. The browser calls your onclick. You never call them back — they call you.
Q6: How do I decide between an abstract method and a hook?
Ask: is this step mandatory for the algorithm to work correctly, or is it optional enhancement?
Use an abstract method when:
- The step is essential — the algorithm cannot produce a meaningful result without it
- Every subclass fundamentally implements it differently (data source, format, destination)
- A missing implementation should be a compile-time error
Use a hook when:
- The step is optional — the algorithm works correctly without any subclass intervention
- Most subclasses don't need this extension point
- The base class has a sensible default behavior (typically a no-op)
- Examples:
beforeRender(),afterLoad(),onProgress(),onError()
A useful heuristic: if the step's base implementation is "do nothing," it's a hook. If the step's base implementation would be "throw NotImplementedError," it's an abstract method.
Q7: How do I prevent subclasses from calling each other's implementations?
You don't need to — and generally shouldn't. Each concrete subclass's implementation is invoked by the base class's template method via polymorphic dispatch. One subclass never calls another's methods directly.
If you find a subclass calling another subclass's method, the design is wrong. Each concrete class should only implement its own step. If two subclasses share some logic, extract that logic into a protected concrete method in the base class — that's exactly what concrete operations in the base class are for.
Q8: Can I use the Template Method Pattern with interfaces instead of abstract classes?
Not directly. An interface in most languages cannot contain a template method — it only declares the contract; it can't provide the algorithm skeleton. Template Method requires a class (abstract or concrete) that can both declare abstract methods and provide concrete implementations in the same type.
Exception: Languages with interface default methods (Java 8+, C# 8+) or trait default implementations (Rust, Kotlin) CAN implement a form of Template Method at the interface/trait level. However, the template method cannot be final in this case — any implementer can override it, weakening the pattern's guarantee.
For strict Template Method semantics (enforced skeleton, prevented template override), use an abstract class.
Q9: When should I refactor from Template Method to Strategy?
Refactor when:
- The number of variants is growing — a deep inheritance hierarchy of 10+ subclasses is harder to manage than 10 strategy objects
- Variants need to be combined — if users should be able to mix and match steps (e.g., JSON reader + HTML renderer), Strategy allows this; Template Method requires a subclass for every combination
- Variants change at runtime — Template Method fixes the variant at compile time (which subclass you instantiate); Strategy lets you swap it at runtime
- You want to test the algorithm separately from the steps — Strategy makes it trivial to mock steps; Template Method requires subclassing to substitute steps in tests
The "Favor composition over inheritance" principle generally points away from Template Method and toward Strategy for complex systems.
Q10: When should I NOT use the Template Method Pattern?
Avoid the Template Method Pattern when:
- The algorithm skeleton itself varies: If the sequence of steps changes between variants (not just how each step is performed), Template Method doesn't fit — consider Chain of Responsibility or a configurable pipeline instead
- You need to combine steps from different hierarchies: Inheritance locks you into one hierarchy; if you want to mix a CSV reader with both HTML and PDF renderers, you'd need two subclasses instead of two strategy compositions
- The inheritance hierarchy would grow too deep: Three or more levels of Template Method inheritance become hard to reason about — refactor to Strategy
- You're in Go or another language that discourages inheritance: Use the interface + free function approach instead
- The algorithm has only one step that varies: The overhead of an abstract class for a single abstract method is usually better served by a direct Strategy injection
Key Takeaways
🎯 Core Concept: The Template Method Pattern defines the skeleton of an algorithm in a base class and defers certain steps to subclasses. The base class controls when each step runs — subclasses control how their specific steps run. The algorithm's structure is invariant; only the implementations of the variable steps change between subclasses. This is the pattern that powers every framework, test runner, and pipeline processor ever built.
🔑 Key Benefits:
- Eliminates algorithm duplication: The sequence of steps lives in exactly one place — the base class — and propagates to every subclass automatically
- Hollywood Principle: The base class calls the subclasses, not the other way around — inversion of control
- Enforced invariants: Steps that must always execute or always run in a fixed order are protected in the base class; subclasses cannot accidentally skip or reorder them
- Open/Closed Principle: Add new variants by adding new subclasses — the base class template never changes
- Readable high-level intent: The template method reads like a recipe — it documents the algorithm's phases without exposing implementation details
🌐 Language-Specific Highlights:
- Python: Always use
ABCand@abstractmethod— without them, forgetting to override a method causes silent runtime failures rather than a clear error at instantiation; document the template method as "do not override" since Python has nofinal - TypeScript: Abstract methods must be
protected, notprivate— subclasses need to implement them; hooks should also beprotectedto prevent external code from calling them out of sequence; useabstract class, not an interface, for the base - Java: Mark the template method
final— this is the single most important Java-specific best practice; never call overridable methods from constructors; useprotectedfor primitive operations - JavaScript: Throw
Errorin "abstract" methods — no language enforcement means missing overrides cause silent failures; regular method syntax (not arrow functions) ensures correct prototype-based inheritance - C#: Use
sealedon the template method; useprotected abstractfor primitive operations andprotected virtualfor hooks; avoidpublicon abstract methods — they expose internal pipeline steps - PHP:
finalon the template method;protected abstractfor primitive operations; PHP 8.1+readonlyconstructor promotion for clean subclass constructors - Go: No inheritance means no classic Template Method — use an interface for the variable steps and a standalone free function as the template method; embed a
BaseReportstruct with no-op hooks to avoid forcing every type to implement them - Rust: Trait methods with default implementations serve as both the template method and hooks; required trait methods serve as abstract primitive operations; use a free function for stricter enforcement since trait default methods can always be overridden
- Dart: Declare primitive operations
abstractin anabstract class; usebase abstract classin Dart 3 to prevent outside classes from bypassing the template viaimplements; hooks have default (empty) implementations in the abstract class - Swift: Use a class (not a protocol) for the base; mark the template method
final; usefatalError("Override this method")in primitive operations since Swift has noabstractkeyword; avoid deep inheritance beyond two levels - Kotlin: Template method is
finalby default in non-openclasses — avoidopenon the template method; useprotected abstractfor primitive operations; useprotected openfor hooks only; useabstract class, not an interface
📋 When to Use Template Method:
- Multiple classes perform the same algorithm with identical step ordering, but vary in how specific steps are implemented
- You want to enforce that certain steps always execute or always execute in a fixed sequence
- You're building a framework or library where the algorithm is fixed but the implementations are provided by the users
- An ETL pipeline, report generator, test lifecycle, or HTTP request handler that shares structure but not implementation
- The number of variants is small and known at compile time
⚠️ When NOT to Use Template Method:
- The algorithm sequence itself varies between variants — Template Method can't handle variable step ordering
- You need to mix and match steps from different hierarchies — use Strategy composition instead
- The inheritance hierarchy is already deep (3+ levels) — further Template Method subclassing is hard to reason about
- You're working in Go or another composition-favoring language — use the interface + free function approach
- The algorithm has only one variable step — a single Strategy injection is simpler than an abstract class
🛠️ Best Practices Across Languages:
- Mark the template method final/sealed: This is the most critical practice — preventing template method override enforces the pattern's core invariant
- Use protected, not public, for primitive operations: External code should never call individual algorithm steps directly — only the template method should orchestrate them
- Keep hooks as no-ops by default: Hooks that do nothing in the base class are safe for subclasses to ignore — don't require override of hooks
- Keep the template method high-level: The template method should read like a recipe — a sequence of named steps. Implementation details belong in the steps themselves
- Never call overridable methods from constructors: In Java and similar languages, this causes initialization order bugs — always call variable steps from the template method, not the constructor
- Extract shared concrete operations: Logic shared by multiple subclasses belongs in a protected concrete method in the base class — not duplicated in each subclass
- Prefer shallow inheritance: Template Method works best with one level of subclassing — if you need subclasses of subclasses, consider switching to Strategy composition
💡 Common Pitfalls:
- No enforcement of abstract methods (Python/JS): Forgetting
ABC/@abstractmethodin Python orthrow Error()in JavaScript means a subclass that forgets to override an abstract step fails silently at runtime, not loudly at definition time - Template method not final: A subclass that accidentally overrides the template method bypasses all shared logic — error handling, logging, metric collection — and creates a subtle, hard-to-diagnose regression
- Abstract methods public instead of protected: External code that calls
report.readData()directly bypasses the template and exposes internal pipeline steps that were never meant to be public API - Too many abstract methods: A template with 8 abstract methods forces every subclass to provide 8 implementations — if most are optional, most should be hooks, not abstract methods
- Calling overridable methods from the constructor (Java): Object initialization order means the subclass fields haven't been set when the constructor runs, causing
NullPointerExceptionwhen the overridden method tries to access subclass state - Deep inheritance hierarchies: Template Method subclasses that are themselves abstract create three-level hierarchies that are difficult to understand and test — flatten with Strategy
🎁 Advanced Techniques:
- Template Method + Strategy hybrid: Use Template Method for the algorithm skeleton and inject Strategy objects for individual steps. The template method calls
this.readStrategy.read()instead ofthis.read()— maximum flexibility with a fixed outer structure - Parameterized template method: Pass configuration to the template method call (e.g.,
generate(title, includeFooter: true)) and use it to conditionally call hooks — feature-flag-style customization without subclassing - Multi-level template: A base class defines a coarse skeleton; a mid-level abstract class adds a finer skeleton for a subset of variants; concrete classes implement the finest-grained steps. Use sparingly — three-level hierarchies are the practical maximum
- Template Method in test fixtures: A
BaseTestclass withsetup(),exerciseUnderTest(), andtearDown()template steps — every test scenario is a concrete subclass that implementsexerciseUnderTest(). The lifecycle is shared; the test action varies. - Framework hooks as Template Method: Every plugin architecture (VS Code extensions, browser extensions, Webpack plugins, Gradle plugins) is a Template Method. The framework defines the lifecycle hooks; plugins fill them in. Understanding this makes framework APIs immediately intuitive.
The Template Method Pattern is the structural backbone of every framework and every algorithm-with-variation ever built. Once you recognize it, you'll see it everywhere: in test runners, build tools, HTTP frameworks, data pipelines, and game loops. It is the clearest expression of the Hollywood Principle in object-oriented design — the framework calls you, you just fill in the blanks.
Want to dive deeper into design patterns? Check out our comprehensive guides on:
- Memento Pattern
- Observer Pattern
- State Pattern
- Strategy Pattern
- Command Pattern Each guide includes examples in all 11 programming languages with language-specific best practices.