StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsTemplate Method

Template Method

A behavioral pattern where a base class writes an algorithm's skeleton once and subclasses fill in only the steps that differ, so shared logic can never drift.

Definition

Two report pipelines share five of their six steps, so somebody copied the file, and now the copies are drifting apart one bugfix at a time. Template Method is the Gang of Four behavioral pattern whose stated intent is to define the skeleton of an algorithm in one method, deferring some steps to subclasses, so variants redefine individual steps without ever touching the algorithm's structure.


A template method is really just a fill-in-the-blanks form. The base class prints the form, the steps in their fixed order, and subclasses write their answers into the blanks, never rewriting the form itself. Shared logic exists once, the variations exist where they differ, and the copy-paste drift that motivated the whole exercise has nowhere left to happen.

The Pipeline, Copied and Drifting

Here's the drift in action. Two miners run the same four-step pipeline, load, parse, total, format, as two independent copies. In March, someone fixed the CSV copy to ignore refund entries. Nobody told the log copy. Trace both outputs, paying attention to the -3:

The CSV miner prints total: 60 with the fix applied, and the log miner prints log total = 9, subtracting a refund the business rule says to ignore, when the right answer is 12. Both files look correct in isolation, which is the cruelty of drift: the bug isn't in either copy, it's in the difference between them, and no file you can open shows you a difference.

One Skeleton, Two Sets of Blanks

The template version writes the pipeline once. ReportMiner.mine() owns the order and the shared totaling logic, including the March fix, while load() and parse() are abstract blanks each miner fills, and format() is a hook with a default. Predict both outputs, and then answer a sharper question: which subclass contains the refund fix?

The outputs are total: 60 and log total = 12, both correct, and the answer to the sharper question is neither. The fix lives in the skeleton, one line in one method, and both miners inherited it the moment it was written. That's the pattern's entire economic argument: shared logic that exists once can't drift, and the subclasses shrink to exactly the code that makes them different.

Who Does What

Two roles, with vocabulary worth keeping straight. The AbstractClass (ReportMiner) owns the template method, the fixed-order skeleton, plus two kinds of steps: primitive operations, abstract and mandatory, and hooks, concrete defaults a subclass may override. ConcreteClasses (CsvMiner, LogMiner) implement the primitives, optionally override hooks, and never touch the template itself.


The pattern works like a tax form: the agency fixes the sections and their order, you write your numbers into the boxes, and nobody rearranges the form. The analogy undersells one inversion worth naming. A paper form sits passive while you work through it, but a template method actively calls your blanks, the framework phoning the subclass rather than the reverse, which the literature christened the Hollywood Principle: don't call us, we'll call you. Every plugin system you've written for runs on that inversion.

Template Method: Step-by-Step

Installing the pattern is four moves:

1. Freeze the Order in One Method

mine() calls the steps in a fixed sequence and lives on the base class, written once. The order is the algorithm's identity, and the template method is its single source of truth.

2. Mark the Blanks Abstract

load() and parse() are declared but not implemented, which makes filling them the price of subclassing. The compiler enforces that every miner answers the questions the skeleton will ask.

3. Offer Hooks With Sensible Defaults

format() ships a default most subclasses keep, and LogMiner overrides it without touching anything else. Hooks are optional blanks, and the abstract-versus-hook split tells subclasses exactly what's expected of them.

4. Seal the Skeleton Itself

The Java and PHP tabs mark mine() final, because a subclass that overrides the template method can reorder the algorithm everyone else relies on. Blanks are for filling, and the form itself is not.
Template Method vs Strategy

You might think this pattern and Strategy are competitors for one job, and they are, which is why the comparison matters more here than usual. Both vary an algorithm, and the standard reference draws the axis: Template Method works at the class level through inheritance, statically, varying the steps inside one fixed structure, while Strategy works at the object level through composition, swapping whole algorithms at runtime. A miner is born a CsvMiner forever, while a checkout changes shipping strategies between two method calls.


Inheritance also bills differently. A subclass gets exactly one parent, so one class follows one template, and mixing two skeletons means restructuring. The same reference lists the pattern's honest cons: clients limited by the provided skeleton, Liskov violations when subclasses suppress steps, and maintenance pain as step counts grow. The practical reading is a default: small stable skeleton with a few varying steps favors this pattern, and anything more dynamic favors handing the algorithm in from outside.

The Go and Rust Versions

Go can't express the pattern as written, having no inheritance, and its translation is quietly the critics' recommendation implemented as syntax: the varying steps become an interface, and the skeleton becomes a plain function driving it, Mine(steps) in the code tab. That's composition doing Template Method's job, structurally closer to Strategy, and Go programmers never notice they've sided with one camp of a design debate.


Rust lands a neater fit than either: a trait with a provided method. The skeleton mine() lives in the trait body with its default implementation, the blanks are the trait's required methods, and hooks are provided methods implementors may override. It's the GoF structure without class inheritance, and since types implement many traits freely, the one-template-per-class rigidity dissolves on the way through.

Best Practices

The pattern is a form and its blanks, and these five keep the form worth filling:


A template of three to five steps reads like a recipe, while one with twelve becomes a framework nobody can implement correctly. The maintenance cost grows with every step, so push detail down into the steps themselves.

Check Yourself

Two questions before you go. First: the refund fix exists in exactly one line of the template version. Walk through how LogMiner got the fix without anyone editing LogMiner, naming the mechanism that routed it there. Second: Template Method and Strategy both vary an algorithm, so which varies at the class level and which at the object level, and what does each choice buy? Both answers are on this page.


Then go spot it in the wild, starting inside a class you've subclassed without thinking about it. Java's InputStream declares single-byte read() abstract and documents that the bulk read method simply calls it repeatedly: implement one blank, inherit the working bulk machinery. Test frameworks run the same form with setup, test, and teardown slots, and every web framework calling your route handler is the Hollywood Principle collecting rent. Last, diff the two most similar files in your codebase. The lines that match are a skeleton waiting to be extracted, and the lines that don't are its blanks introducing themselves.

the_drifted_copies.py

class CsvMiner:
    def mine(self) -> str:
        raw = "10,20,30"
        numbers = [int(part) for part in raw.split(",")]
        total = 0
        for n in numbers:
            if n >= 0:  # bugfix from March: ignore refunds
                total += n
        return "total: " + str(total)

class LogMiner:
    def mine(self) -> str:
        raw = "sale:5 sale:-3 sale:7"
        numbers = [int(entry.split(":")[1]) for entry in raw.split()]
        total = 0
        for n in numbers:
            total += n  # the March bugfix never made it to this copy
        return "log total = " + str(total)

# Example usage
print(CsvMiner().mine())  # total: 60
print(LogMiner().mine())  # log total = 9  <- refund subtracted: wrong

template_method.py

from abc import ABC, abstractmethod

class ReportMiner(ABC):  # AbstractClass: owns the skeleton
    def mine(self) -> str:
        # The template method: fixed order, never overridden
        raw = self.load()
        numbers = self.parse(raw)
        total = sum(n for n in numbers if n >= 0)  # March bugfix, ONE place
        return self.format(total)

    @abstractmethod
    def load(self) -> str: ...  # primitive: every subclass must fill this

    @abstractmethod
    def parse(self, raw: str) -> list[int]: ...  # primitive

    def format(self, total: int) -> str:  # hook: override only if needed
        return "total: " + str(total)

class CsvMiner(ReportMiner):  # ConcreteClass: fills the blanks only
    def load(self) -> str:
        return "10,20,30"

    def parse(self, raw: str) -> list[int]:
        return [int(part) for part in raw.split(",")]

class LogMiner(ReportMiner):  # ConcreteClass that also overrides the hook
    def load(self) -> str:
        return "sale:5 sale:-3 sale:7"

    def parse(self, raw: str) -> list[int]:
        return [int(entry.split(":")[1]) for entry in raw.split()]

    def format(self, total: int) -> str:
        return "log total = " + str(total)

# Example usage: one skeleton runs, the blanks differ
print(CsvMiner().mine())  # total: 60
print(LogMiner().mine())  # log total = 12