Behavioral Pattern

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?

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:

  1. 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
  2. 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
  3. Open/Closed Principle: Add new algorithm variants by adding new subclasses — the base class template never changes
  4. 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
  5. Hook Methods for Optional Extension: Subclasses can opt into additional behavior via hooks — empty base implementations that subclasses override only when needed
  6. 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:

PatternMechanismWho Varies BehaviorCouplingUse Case
Template MethodInheritanceSubclass overrides stepsHigh (base/sub relationship)Algorithm skeleton with variable steps
StrategyCompositionClient injects strategy objectLow (interface-based)Interchangeable algorithms at runtime
Factory MethodInheritanceSubclass creates the objectHigh (base/sub relationship)Object creation with variable product type
Hook MethodInheritanceSubclass opts into extensionHigh (base/sub relationship)Optional extensions to a fixed process
BuilderCompositionBuilder creates step-by-stepLow (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:

  1. 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
  2. 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: final on the method
  • C#: sealed override (or just the method in a non-virtual-by-default context)
  • PHP: final keyword on the method
  • Swift: final on 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 ABC and @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 no final
  • TypeScript: Abstract methods must be protected, not private — subclasses need to implement them; hooks should also be protected to prevent external code from calling them out of sequence; use abstract 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; use protected for primitive operations
  • JavaScript: Throw Error in "abstract" methods — no language enforcement means missing overrides cause silent failures; regular method syntax (not arrow functions) ensures correct prototype-based inheritance
  • C#: Use sealed on the template method; use protected abstract for primitive operations and protected virtual for hooks; avoid public on abstract methods — they expose internal pipeline steps
  • PHP: final on the template method; protected abstract for primitive operations; PHP 8.1+ readonly constructor 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 BaseReport struct 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 abstract in an abstract class; use base abstract class in Dart 3 to prevent outside classes from bypassing the template via implements; hooks have default (empty) implementations in the abstract class
  • Swift: Use a class (not a protocol) for the base; mark the template method final; use fatalError("Override this method") in primitive operations since Swift has no abstract keyword; avoid deep inheritance beyond two levels
  • Kotlin: Template method is final by default in non-open classes — avoid open on the template method; use protected abstract for primitive operations; use protected open for hooks only; use abstract 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:

  1. Mark the template method final/sealed: This is the most critical practice — preventing template method override enforces the pattern's core invariant
  2. Use protected, not public, for primitive operations: External code should never call individual algorithm steps directly — only the template method should orchestrate them
  3. 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
  4. 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
  5. 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
  6. Extract shared concrete operations: Logic shared by multiple subclasses belongs in a protected concrete method in the base class — not duplicated in each subclass
  7. 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/@abstractmethod in Python or throw 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 NullPointerException when 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 of this.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 BaseTest class with setup(), exerciseUnderTest(), and tearDown() template steps — every test scenario is a concrete subclass that implements exerciseUnderTest(). 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: