Template Method Pattern: Define Algorithm Skeleton
TL;DR
The Template Method Pattern defines the skeleton of an algorithm in a base class, deferring specific steps to subclasses. Subclasses can override certain steps without changing the algorithm’s overall structure. This pattern promotes code reuse and enforces a consistent process across variations.
Core Concept
What is the Template Method Pattern?
The Template Method Pattern is a behavioral design pattern that defines the structure of an algorithm in a parent class but lets subclasses override specific steps without changing the algorithm’s flow. The parent class contains a template method that calls a series of steps, some of which are concrete (implemented in the parent) and others are abstract (must be implemented by subclasses).
Why It Matters
This pattern solves a common problem: you have multiple classes that perform similar operations with slight variations. Without this pattern, you’d duplicate the common logic across classes, violating the DRY (Don’t Repeat Yourself) principle. The Template Method Pattern extracts the common structure into a base class, making your code more maintainable and consistent.
Key Components
Abstract Class: Contains the template method and defines the algorithm’s skeleton. It may include:
- Template method (usually final/non-overridable): Orchestrates the algorithm steps
- Abstract methods: Steps that subclasses must implement
- Hook methods: Optional steps with default (often empty) implementations that subclasses can override
- Concrete methods: Common steps implemented in the base class
Concrete Classes: Inherit from the abstract class and provide specific implementations for abstract steps.
How It Works
The template method calls a sequence of methods in a specific order. Some methods are already implemented (shared behavior), while others are placeholders that subclasses fill in (variable behavior). This inverts the typical control flow — instead of subclasses calling parent methods, the parent’s template method calls subclass methods (the Hollywood Principle: “Don’t call us, we’ll call you”).
When to Use It
- Multiple classes share the same algorithm structure but differ in specific steps
- You want to enforce a consistent process across variations
- You need to prevent subclasses from changing the algorithm’s overall flow
- You want to extract common code from similar classes into a parent class
Visual Guide
Template Method Pattern Structure
classDiagram
class AbstractClass {
<<abstract>>
+templateMethod() final
#step1()*
#step2()
#step3()*
#hook()
}
class ConcreteClassA {
#step1()
#step3()
#hook()
}
class ConcreteClassB {
#step1()
#step3()
}
AbstractClass <|-- ConcreteClassA
AbstractClass <|-- ConcreteClassB
note for AbstractClass "templateMethod() calls:\n1. step1()\n2. step2()\n3. hook()\n4. step3()"
*The abstract class defines the template method that orchestrates the algorithm. Concrete classes implement the abstract steps (marked with ) and optionally override hooks.
Template Method Execution Flow
sequenceDiagram
participant Client
participant ConcreteClass
participant AbstractClass
Client->>ConcreteClass: templateMethod()
ConcreteClass->>AbstractClass: templateMethod()
AbstractClass->>ConcreteClass: step1() [abstract]
ConcreteClass-->>AbstractClass: result
AbstractClass->>AbstractClass: step2() [concrete]
AbstractClass->>ConcreteClass: hook() [optional]
ConcreteClass-->>AbstractClass: result
AbstractClass->>ConcreteClass: step3() [abstract]
ConcreteClass-->>AbstractClass: result
AbstractClass-->>Client: final result
The template method in the parent class controls the flow, calling both concrete methods (implemented in parent) and abstract methods (implemented in subclasses).
Examples
Example 1: Data Processing Pipeline
Let’s build a data processing system where different file types follow the same workflow: read data, process it, and save results.
from abc import ABC, abstractmethod
class DataProcessor(ABC):
"""Abstract class defining the data processing template."""
def process(self):
"""Template method - defines the algorithm skeleton."""
print("Starting data processing...")
data = self.read_data()
print(f"Read data: {data}")
processed = self.transform_data(data)
print(f"Transformed data: {processed}")
if self.should_validate():
self.validate_data(processed)
self.save_data(processed)
print("Processing complete!\n")
@abstractmethod
def read_data(self):
"""Abstract method - subclasses must implement."""
pass
@abstractmethod
def transform_data(self, data):
"""Abstract method - subclasses must implement."""
pass
@abstractmethod
def save_data(self, data):
"""Abstract method - subclasses must implement."""
pass
def should_validate(self):
"""Hook method - subclasses can override (default: True)."""
return True
def validate_data(self, data):
"""Hook method - subclasses can override."""
print("Validating data (default validation)...")
class CSVProcessor(DataProcessor):
"""Concrete implementation for CSV files."""
def read_data(self):
return "csv,data,rows"
def transform_data(self, data):
return data.upper()
def save_data(self, data):
print(f"Saving to CSV: {data}")
def validate_data(self, data):
print("CSV-specific validation: checking delimiter...")
class JSONProcessor(DataProcessor):
"""Concrete implementation for JSON files."""
def read_data(self):
return '{"key": "value"}'
def transform_data(self, data):
return data.replace('"', "'")
def save_data(self, data):
print(f"Saving to JSON: {data}")
def should_validate(self):
# Override hook to skip validation
return False
# Usage
print("=== CSV Processing ===")
csv_processor = CSVProcessor()
csv_processor.process()
print("=== JSON Processing ===")
json_processor = JSONProcessor()
json_processor.process()
Expected Output:
=== CSV Processing ===
Starting data processing...
Read data: csv,data,rows
Transformed data: CSV,DATA,ROWS
Validating data (default validation)...
CSV-specific validation: checking delimiter...
Saving to CSV: CSV,DATA,ROWS
Processing complete!
=== JSON Processing ===
Starting data processing...
Read data: {"key": "value"}
Transformed data: {'key': 'value'}
Saving to JSON: {'key': 'value'}
Processing complete!
Key Points:
- The
process()method is the template method that defines the workflow read_data(),transform_data(), andsave_data()are abstract — subclasses must implement themshould_validate()andvalidate_data()are hooks — subclasses can optionally override them- The algorithm structure is consistent, but specific steps vary by file type
Try it yourself: Add an XMLProcessor class that reads XML data, transforms it by removing whitespace, and saves it with custom validation.
Example 2: Game Character AI
Let’s create an AI system for game characters where all characters follow the same decision-making process but with different behaviors.
from abc import ABC, abstractmethod
import random
class CharacterAI(ABC):
"""Template for character AI behavior."""
def __init__(self, name):
self.name = name
self.health = 100
def take_turn(self, enemy):
"""Template method - defines AI turn structure."""
print(f"\n{self.name}'s turn:")
self.assess_situation(enemy)
if self.should_use_special_ability():
self.use_special_ability(enemy)
else:
action = self.choose_action(enemy)
self.execute_action(action, enemy)
self.end_turn()
def assess_situation(self, enemy):
"""Concrete method - common to all characters."""
print(f" Assessing: My HP={self.health}, Enemy HP={enemy.health}")
@abstractmethod
def choose_action(self, enemy):
"""Abstract method - each character type decides differently."""
pass
@abstractmethod
def execute_action(self, action, enemy):
"""Abstract method - each character executes actions differently."""
pass
def should_use_special_ability(self):
"""Hook method - default implementation."""
return self.health < 30
def use_special_ability(self, enemy):
"""Hook method - can be overridden."""
print(" Using generic special ability!")
def end_turn(self):
"""Concrete method - common cleanup."""
print(f" {self.name} ends turn.")
class WarriorAI(CharacterAI):
"""Aggressive melee fighter."""
def choose_action(self, enemy):
if enemy.health < 40:
return "heavy_attack"
return "normal_attack"
def execute_action(self, action, enemy):
if action == "heavy_attack":
damage = 30
print(f" {self.name} uses HEAVY ATTACK!")
else:
damage = 15
print(f" {self.name} attacks normally.")
enemy.health -= damage
print(f" Dealt {damage} damage. Enemy HP: {enemy.health}")
def use_special_ability(self, enemy):
print(f" {self.name} uses BERSERKER RAGE! (Double damage next turn)")
class MageAI(CharacterAI):
"""Strategic spellcaster."""
def choose_action(self, enemy):
if self.health > 70:
return "offensive_spell"
return "defensive_spell"
def execute_action(self, action, enemy):
if action == "offensive_spell":
damage = 25
enemy.health -= damage
print(f" {self.name} casts FIREBALL! Dealt {damage} damage.")
else:
heal = 20
self.health = min(100, self.health + heal)
print(f" {self.name} casts HEAL! Restored {heal} HP.")
def should_use_special_ability(self):
# Mages use special ability more conservatively
return self.health < 20
def use_special_ability(self, enemy):
print(f" {self.name} casts METEOR STORM! (Massive AOE damage)")
class RogueAI(CharacterAI):
"""Cunning opportunist."""
def choose_action(self, enemy):
# Rogues choose randomly for unpredictability
return random.choice(["backstab", "poison", "evade"])
def execute_action(self, action, enemy):
if action == "backstab":
damage = 35
enemy.health -= damage
print(f" {self.name} BACKSTABS! Critical hit: {damage} damage.")
elif action == "poison":
damage = 10
enemy.health -= damage
print(f" {self.name} applies POISON. {damage} damage + DoT.")
else:
print(f" {self.name} EVADES and repositions.")
def should_use_special_ability(self):
# Rogues never use the default special ability
return False
# Simulate combat
warrior = WarriorAI("Thorin")
mage = MageAI("Gandalf")
rogue = RogueAI("Shadow")
# Create a dummy enemy
class Enemy:
def __init__(self):
self.health = 100
enemy = Enemy()
print("=== Combat Simulation ===")
warrior.take_turn(enemy)
mage.health = 60 # Simulate damage
mage.take_turn(enemy)
rogue.take_turn(enemy)
# Test special ability trigger
warrior.health = 25
print("\n=== Low Health Scenario ===")
warrior.take_turn(enemy)
Expected Output (Rogue actions vary due to randomness):
=== Combat Simulation ===
Thorin's turn:
Assessing: My HP=100, Enemy HP=100
Thorin attacks normally.
Dealt 15 damage. Enemy HP: 85
Thorin ends turn.
Gandalf's turn:
Assessing: My HP=60, Enemy HP=85
Gandalf casts HEAL! Restored 20 HP.
Gandalf ends turn.
Shadow's turn:
Assessing: My HP=100, Enemy HP=85
Shadow applies POISON. 10 damage + DoT.
Shadow ends turn.
=== Low Health Scenario ===
Thorin's turn:
Assessing: My HP=25, Enemy HP=75
Thorin uses BERSERKER RAGE! (Double damage next turn)
Thorin ends turn.
Key Points:
- All characters follow the same turn structure: assess → choose → execute → end
- Each character class implements
choose_action()andexecute_action()differently - Hook methods (
should_use_special_ability(),use_special_ability()) provide optional customization - The Rogue overrides
should_use_special_ability()to completely disable the feature
Java/C++ Notes:
- In Java, mark the template method as
finalto prevent overriding:public final void takeAction() - In C++, you can’t prevent overriding, but document the template method clearly
- Use
protectedvisibility for methods meant to be overridden by subclasses
Try it yourself: Add a HealerAI class that prioritizes healing when health is below 50% and only attacks when health is above 80%. Override the end_turn() method to display a healing message.
Common Mistakes
1. Making the Template Method Overridable
Mistake: Allowing subclasses to override the template method itself, which breaks the pattern’s purpose.
# WRONG - template method can be overridden
class DataProcessor(ABC):
def process(self): # Should be final/non-overridable
self.read()
self.transform()
class BadProcessor(DataProcessor):
def process(self): # Breaks the algorithm structure!
self.transform() # Skips read step
Why it’s wrong: The entire point is to enforce a consistent algorithm structure. If subclasses can override the template method, they can change the order of steps or skip steps entirely.
Fix: In Python, document that the template method shouldn’t be overridden. In Java, use the final keyword. In C++, document clearly and consider using the Non-Virtual Interface (NVI) idiom.
2. Making All Methods Abstract
Mistake: Defining every method as abstract, missing the opportunity to share common code.
# WRONG - no code reuse
class DataProcessor(ABC):
@abstractmethod
def read_data(self): pass
@abstractmethod
def validate_data(self): pass # Could be shared!
@abstractmethod
def log_progress(self): pass # Could be shared!
Why it’s wrong: You lose the benefits of code reuse. If validation and logging are identical across subclasses, implement them in the base class.
Fix: Only make methods abstract when they truly need different implementations. Provide concrete implementations for common behavior.
3. Confusing Template Method with Strategy Pattern
Mistake: Using Template Method when Strategy Pattern is more appropriate, or vice versa.
Template Method: Use when you have a fixed algorithm structure with varying steps (inheritance-based). Strategy Pattern: Use when you want to swap entire algorithms at runtime (composition-based).
# Template Method - algorithm structure is fixed
class ReportGenerator(ABC):
def generate(self):
self.gather_data()
self.format_data() # Step varies
self.output() # Step varies
# Strategy Pattern - entire algorithm can be swapped
class ReportGenerator:
def __init__(self, formatter_strategy):
self.formatter = formatter_strategy
def generate(self):
self.formatter.format_and_output() # Entire process delegated
Why it matters: Template Method creates tight coupling through inheritance. Strategy Pattern offers more flexibility through composition. Choose based on whether you need a fixed process with variable steps (Template Method) or completely interchangeable algorithms (Strategy).
4. Forgetting to Provide Hook Methods
Mistake: Making every variable step abstract, forcing subclasses to implement methods they don’t need.
# WRONG - forces unnecessary implementation
class DataProcessor(ABC):
@abstractmethod
def pre_process(self): pass # Not all processors need this
@abstractmethod
def post_process(self): pass # Not all processors need this
class SimpleProcessor(DataProcessor):
def pre_process(self):
pass # Empty implementation - waste of code
def post_process(self):
pass # Empty implementation - waste of code
Fix: Use hook methods with default (often empty) implementations for optional steps.
# CORRECT - optional hooks
class DataProcessor(ABC):
def pre_process(self): # Hook with default empty implementation
pass
def post_process(self): # Hook with default empty implementation
pass
5. Creating Too Many Levels of Abstraction
Mistake: Building deep inheritance hierarchies with multiple template methods at different levels.
# WRONG - overly complex hierarchy
class BaseProcessor(ABC): # Template method here
def process(self): ...
class MiddleProcessor(BaseProcessor): # Another template method here
def advanced_process(self): ...
class ConcreteProcessor(MiddleProcessor): # Confusing!
def specific_process(self): ...
Why it’s wrong: Deep hierarchies become hard to understand and maintain. It’s unclear which template method controls what.
Fix: Keep hierarchies shallow (typically 2 levels: abstract base and concrete implementations). If you need more complexity, consider composition or other patterns.
Interview Tips
What Interviewers Look For
1. Pattern Recognition
Interviewers often present a scenario with code duplication and ask how you’d refactor it. Look for these signals:
- Multiple classes with similar workflows but different details
- Repeated algorithm structure across classes
- Need to enforce a consistent process
Example question: “We have three report generators that all fetch data, format it, and export it, but each does formatting differently. How would you structure this?”
Answer approach: “I’d use the Template Method Pattern. Create an abstract ReportGenerator with a generate() template method that calls fetch_data(), format_data(), and export_data(). The format_data() method would be abstract, while fetch_data() and export_data() could be concrete if they’re shared.”
2. Explaining the Hollywood Principle
Be ready to explain: “Don’t call us, we’ll call you.” This is the inversion of control that defines Template Method.
Good explanation: “In Template Method, the parent class’s template method calls subclass methods, not the other way around. The parent controls the flow, and subclasses just fill in specific steps. This is the opposite of typical inheritance where subclasses call parent methods.”
3. Comparing with Strategy Pattern
This is a common interview question: “When would you use Template Method vs. Strategy Pattern?”
Strong answer:
- Template Method: When you have a fixed algorithm structure and want to vary specific steps. Uses inheritance. Decided at compile-time.
- Strategy Pattern: When you want to swap entire algorithms at runtime. Uses composition. More flexible but more complex.
- Example: “For a payment processing system with fixed steps (validate, charge, notify), use Template Method. For a sorting system where you might switch between quicksort and mergesort at runtime, use Strategy.”
4. Discussing Trade-offs
Interviewers want to know you understand the pattern’s limitations:
Advantages:
- Eliminates code duplication
- Enforces consistent algorithm structure
- Easy to add new variations (just add a subclass)
Disadvantages:
- Creates tight coupling through inheritance
- Can be harder to understand than simple duplication for simple cases
- Violates Liskov Substitution Principle if subclasses change behavior significantly
When to avoid: “If the algorithm structure varies significantly between implementations, Template Method creates artificial constraints. Also avoid for very simple cases where the pattern adds more complexity than it removes.”
5. Live Coding Tips
If asked to implement Template Method:
- Start with the abstract class and template method first — this shows you understand the pattern’s structure
- Identify which methods should be abstract vs. concrete — explain your reasoning
- Add at least one hook method — shows you understand optional customization
- Make the template method non-overridable — in Python, add a docstring note; in Java, use
final - Create 2-3 concrete implementations — demonstrates the pattern’s value
Common interview scenario: “Design a testing framework where all tests follow the same lifecycle: setup, execute, teardown, but each test type has different execution logic.”
class TestCase(ABC):
def run(self): # Template method
self.setup()
try:
self.execute() # Abstract - varies by test type
finally:
self.teardown()
def setup(self): # Hook
pass
@abstractmethod
def execute(self):
pass
def teardown(self): # Hook
pass
6. Red Flags to Avoid
- Don’t say “Template Method and Strategy are the same” — they’re fundamentally different
- Don’t make the template method abstract — it should be concrete and call other methods
- Don’t forget to mention the Hollywood Principle — it’s a key concept
- Don’t overcomplicate — if asked for a simple example, keep it simple
Practice Question:
“You’re building a data import system. All imports follow the same process: validate file, parse data, transform data, load into database. However, CSV, JSON, and XML files parse differently. How would you design this?”
Strong answer structure:
- Identify this as Template Method (fixed process, varying steps)
- Sketch the abstract class with
import_data()as template method - Make
parse_data()abstract (varies by format) - Make
validate_file(),transform_data(), andload_to_db()concrete (shared logic) - Add a hook like
should_validate_schema()for optional behavior - Mention you’d create
CSVImporter,JSONImporter,XMLImportersubclasses
Key Takeaways
-
Template Method Pattern defines an algorithm’s skeleton in a base class, with subclasses implementing specific steps without changing the overall structure.
-
Use abstract methods for required variations (steps that must differ between subclasses) and hook methods for optional variations (steps that can optionally be customized).
-
The template method should be non-overridable to enforce the algorithm’s structure — in Java use
final, in Python document clearly. -
Template Method uses inheritance (compile-time binding) while Strategy Pattern uses composition (runtime binding) — choose Template Method when you have a fixed process with varying steps.
-
The Hollywood Principle (“Don’t call us, we’ll call you”) is key — the parent’s template method controls the flow and calls subclass methods, inverting the typical control flow of inheritance.