Define the skeleton of an algorithm in an operation, deferring some steps to subclasses so they can redefine those steps without changing the algorithm's structure.
01
The problem Template Method solves
The problem
Three nightly import jobs. One pulls a CSV from an SFTP drop, one calls a partner's REST API, one reads a replica database. Everything else about them is identical: acquire a lock so two runs can't overlap, fetch, validate the rows, transform to the internal schema, write in batches, record metrics, release the lock, and on failure alert the on-call channel with the job name.
They were written three months apart by three people, so you have three copies of that sequence. They are not quite the same. The API job releases the lock in a finally; the CSV job doesn't, so a crash leaves it locked until someone notices at 6am. Two of the three record metrics before validation, one after, which is why the dashboards disagree. Only one alerts on failure.
The part that genuinely differs between these jobs is fetch and parse — maybe forty lines out of three hundred. The other two hundred and sixty are the same idea, written three times, wrong in three different ways.
02
How the Template Method pattern works
Write the sequence once, in a base class, and leave named holes where the jobs actually differ:
A base class holds the template method: the full algorithm, in order, calling steps by name. Mark it final so no subclass can reorder it.
Steps that every job must supply are abstract — fetch(), parse(). The compiler makes them mandatory.
Steps most jobs share get a default implementation. Steps some jobs want are hooks: empty methods a subclass may override and usually doesn't.
Subclasses supply the differences and nothing else. The lock, the ordering, the metrics and the failure alert exist once, and every job gets the fixed version.
This is the Hollywood Principle: "don't call us, we'll call you". Your subclass never calls the framework — the framework calls your subclass, at a moment it chooses. That inversion is what separates a library from a framework, and it's why setUp() in a test class, render() in a React component and doGet() in a servlet all feel the same: you are filling in a hole in someone else's algorithm. It also explains the pattern's main frustration — you get control only at the points the base class decided to offer.
Participants. The Abstract Class holds the template method and declares the primitive operations. Each Concrete Class implements only the primitives. Note the final: the subclass supplies steps, never the sequence — which is precisely the guarantee the three hand-written jobs failed to give.
03
See it: same skeleton, different blanks
The scheduler calls job.run() — the fixed sequence, every time. Choose which import runs and watch which subclass fills the two blanks, while the lock, the validation and the failure alert stay exactly where they are.
▶ Try it — run each import
An interactive template method: the scheduler always calls the same fixed run() sequence — lock, fetch, parse, validate, write, metrics, unlock — and CsvImportJob, ApiImportJob or DbImportJob supplies only the fetch and parse steps.
Add the Kafka job and read the counter: zero edits to the base class. That new job cannot forget to release the lock, cannot record metrics in the wrong place, and cannot skip the failure alert — not because its author was careful, but because those decisions are not available to them. That guarantee is what the pattern is actually selling.
04
Template Method code examples
A sealed skeleton, mandatory blanks, and optional hooks — plus what to do in a language with no inheritance.
public abstract class ImportJob {
/** final: subclasses fill the steps, they never reorder them. */
public final ImportResult run() {
lock.acquire(name());
try {
RawBatch raw = fetch(); // abstract — every job must supply
List<Row> rows = parse(raw); // abstract
validate(rows); // default, overridable
beforeWrite(rows); // hook — usually does nothing
ImportResult result = writeInBatches(rows);
metrics.record(name(), result);
return result;
} catch (Exception e) {
alerts.page(name(), e); // one place, so no job can forget
throw e;
} finally {
lock.release(name()); // ditto
}
}
protected abstract String name();
protected abstract RawBatch fetch();
protected abstract List<Row> parse(RawBatch raw);
/** Default step: most jobs are happy with this. */
protected void validate(List<Row> rows) {
rows.forEach(Row::requireMandatoryFields);
}
/** Hook: deliberately empty. Override only if you need it. */
protected void beforeWrite(List<Row> rows) { }
}
public final class CsvImportJob extends ImportJob {
protected String name() { return "csv-import"; }
protected RawBatch fetch() { return sftp.download(path); }
protected List<Row> parse(RawBatch raw) { return CsvParser.parse(raw); }
}
// HttpServlet.service() is the JDK's own template method: it inspects the HTTP
// verb and calls doGet, doPost or doPut — the hooks you actually override.
from abc import ABC, abstractmethod
from typing import final
class ImportJob(ABC):
@final # advisory: type checkers enforce it, Python doesn't
def run(self) -> ImportResult:
lock.acquire(self.name)
try:
raw = self.fetch()
rows = self.parse(raw)
self.validate(rows)
self.before_write(rows) # hook
result = self.write_in_batches(rows)
metrics.record(self.name, result)
return result
except Exception as exc:
alerts.page(self.name, exc)
raise
finally:
lock.release(self.name)
@property
@abstractmethod
def name(self) -> str: ...
@abstractmethod
def fetch(self) -> RawBatch: ...
@abstractmethod
def parse(self, raw: RawBatch) -> list[Row]: ...
def validate(self, rows: list[Row]) -> None:
for row in rows:
row.require_mandatory_fields()
def before_write(self, rows: list[Row]) -> None:
"""Hook. Deliberately empty."""
# Two Python-specific notes. `@final` is checked by mypy, not by the runtime —
# a determined subclass can still override run(), so the guarantee is weaker
# than Java's. And unittest.TestCase is the template method every Python
# developer has already used: run() calls setUp, then your test_*, then
# tearDown, in an order you don't control and shouldn't want to.
class ImportJob {
public:
virtual ~ImportJob() = default;
// NON-VIRTUAL INTERFACE: the public entry point is non-virtual, so it
// cannot be overridden, and the customisation points are PRIVATE virtuals.
// This is the standard C++ idiom for Template Method, and it's stricter
// than the Java version — a subclass can supply steps but cannot even see
// the skeleton, let alone replace it.
ImportResult run() {
Lock guard{name()};
try {
auto raw = fetch();
auto rows = parse(raw);
validate(rows);
beforeWrite(rows);
auto result = writeInBatches(rows);
metrics::record(name(), result);
return result;
} catch (const std::exception& e) {
alerts::page(name(), e);
throw;
}
// Lock's destructor releases — RAII means "finally" isn't needed.
}
private:
virtual std::string name() const = 0;
virtual RawBatch fetch() = 0;
virtual std::vector<Row> parse(const RawBatch&) = 0;
virtual void validate(const std::vector<Row>& rows) {
for (const auto& r : rows) r.requireMandatoryFields();
}
virtual void beforeWrite(const std::vector<Row>&) {} // hook
};
// The compile-time variant, when you don't want virtual dispatch at all:
// CRTP — `template <class Derived> class ImportJob { ... static_cast<Derived*>(this)->fetch(); }`
public abstract class ImportJob
{
// No `virtual` = it cannot be overridden. C# makes non-virtual the default,
// so the skeleton is sealed unless you deliberately open it.
public async Task<ImportResult> RunAsync(CancellationToken ct = default)
{
await _lock.AcquireAsync(Name, ct);
try
{
var raw = await FetchAsync(ct);
var rows = Parse(raw);
Validate(rows);
BeforeWrite(rows); // hook
var result = await WriteInBatchesAsync(rows, ct);
_metrics.Record(Name, result);
return result;
}
catch (Exception ex)
{
_alerts.Page(Name, ex);
throw;
}
finally
{
await _lock.ReleaseAsync(Name);
}
}
protected abstract string Name { get; }
protected abstract Task<RawBatch> FetchAsync(CancellationToken ct);
protected abstract IReadOnlyList<Row> Parse(RawBatch raw);
protected virtual void Validate(IReadOnlyList<Row> rows) { /* default */ }
protected virtual void BeforeWrite(IReadOnlyList<Row> rows) { } // hook
}
// `protected` matters here: these are extension points for subclasses, not
// public API. Making them public invites callers to run half the algorithm,
// which is exactly the thing the template method is protecting.
// Classes work, but JavaScript's honest version of this pattern is usually a
// function that takes the varying steps — no inheritance, no `super`, and the
// skeleton stays just as fixed.
export const runImport = async ({ name, fetch, parse, validate = defaultValidate, beforeWrite }) => {
await lock.acquire(name)
try {
const raw = await fetch()
const rows = parse(raw)
validate(rows)
beforeWrite?.(rows) // hook, optional by construction
const result = await writeInBatches(rows)
metrics.record(name, result)
return result
} catch (err) {
alerts.page(name, err)
throw err
} finally {
await lock.release(name)
}
}
const csvImport = {
name: 'csv-import',
fetch: () => sftp.download(path),
parse: raw => parseCsv(raw),
}
await runImport(csvImport)
// Same guarantees, one less concept — and the caller can't reorder the steps
// because they never see them. React class components are the inheritance
// version most JS developers have met: you write render() and componentDidMount()
// and React decides when they run, which is the Hollywood Principle exactly.
package importer
// Go has no inheritance, so Template Method inverts: the skeleton is a FUNCTION
// and the blanks are an interface. The guarantee is identical — callers can't
// reorder steps they never see — and it composes better than a base class.
type Source interface {
Name() string
Fetch(ctx context.Context) (RawBatch, error)
Parse(RawBatch) ([]Row, error)
}
// Optional steps become optional interfaces, checked with a type assertion —
// Go's equivalent of an overridable hook.
type Validator interface{ Validate([]Row) error }
func Run(ctx context.Context, src Source) (result ImportResult, err error) {
if err = lock.Acquire(ctx, src.Name()); err != nil {
return ImportResult{}, err
}
defer lock.Release(src.Name()) // cannot be forgotten
defer func() {
if err != nil {
alerts.Page(src.Name(), err)
}
}()
raw, err := src.Fetch(ctx)
if err != nil {
return ImportResult{}, fmt.Errorf("fetch: %w", err)
}
rows, err := src.Parse(raw)
if err != nil {
return ImportResult{}, fmt.Errorf("parse: %w", err)
}
if v, ok := src.(Validator); ok { // the hook, if the source wants it
if err = v.Validate(rows); err != nil {
return ImportResult{}, err
}
}
return writeInBatches(ctx, rows)
}
// sort.Sort is the standard library's template method: the algorithm is fixed
// and you supply Len, Less and Swap.
abstract class ImportJob {
// TypeScript has no `final`, so the convention is to keep the template public
// and the steps protected — and to say so in a comment, because the compiler
// will not stop a subclass overriding run().
async run(): Promise<ImportResult> {
await lock.acquire(this.name)
try {
const raw = await this.fetch()
const rows = this.parse(raw)
this.validate(rows)
this.beforeWrite(rows)
const result = await writeInBatches(rows)
metrics.record(this.name, result)
return result
} catch (err) {
alerts.page(this.name, err)
throw err
} finally {
await lock.release(this.name)
}
}
protected abstract get name(): string
protected abstract fetch(): Promise<RawBatch>
protected abstract parse(raw: RawBatch): Row[]
protected validate(rows: Row[]): void { rows.forEach(requireMandatoryFields) }
protected beforeWrite(_rows: Row[]): void {}
}
// The composition version types the holes precisely, and needs no class at all:
type ImportSteps = {
readonly name: string
fetch(): Promise<RawBatch>
parse(raw: RawBatch): Row[]
validate?(rows: Row[]): void // `?` makes "hook" a type, not a convention
}
export const runImport = async (steps: ImportSteps): Promise<ImportResult> => { /* … */ }
Read across the tabs: C++ has the strictest version — the non-virtual interface idiom, where the public entry point can't be overridden and the customisation points are private virtuals. Go can't do it by inheritance at all, so it inverts the pattern into a function plus an interface, with optional steps as optional interfaces — and that inversion is worth stealing in every language, because it composes where a base class doesn't. Python's @final and TypeScript's absence of one are the honest caveat: in those languages "the subclass may not reorder the steps" is a convention, not a guarantee.
05
How to implement Template Method
Write out the algorithm you're duplicating and mark which lines actually differ between the copies. If almost everything differs, this is the wrong pattern.
Put the invariant sequence in the template method and seal it — final, non-virtual, or the non-virtual-interface idiom.
Make genuinely required steps abstract, so the compiler forces every subclass to supply them.
Give common steps a sensible default, and add hooks — empty methods — only where a real subclass needs one. Speculative hooks are dead weight.
Keep the steps protected: they're extension points, not public API. A public step invites callers to run half the algorithm.
Name steps for what they achieve, not when they run — fetch(), not step2().
Prefer the composition form (a function taking the steps) when subclasses would otherwise inherit state or behaviour they don't need.
06
When to use Template Method — and when not to
Use it when several implementations share an algorithm's structure and differ only in specific steps; when you want to factor out duplication and be certain each variant follows the same order; or when you're building a framework and need to control the flow while letting users supply the parts. Test runners, request lifecycles, ETL jobs and rendering pipelines are its natural home.
Where it goes wrong
The fragile base class. Every subclass depends on the base class's internals and call order, so an innocuous change to the skeleton can break subclasses you didn't know existed — sometimes in other repositories. This is inheritance's oldest problem and the pattern sits right on top of it.
Hook explosion. Every new requirement adds a beforeX, afterY, shouldZ, until the base class is a soup of empty methods and the flow is impossible to follow. If you have more than a few hooks, you wanted composition.
Deep hierarchies.ImportJob → BatchImportJob → IncrementalBatchImportJob → CsvIncrementalBatchImportJob. Following one execution now means reading four files and knowing which override wins. Keep it to one level.
Subclasses forced to implement steps they don't have. A job with nothing to validate implementing an empty validate() is a sign the template covers two different algorithms.
A template that isn't shared. A base class with one subclass is indirection with no payoff. Wait for the second real implementation.
You want to…
Use
Because
Fix an algorithm's steps and vary specific ones
Template Method
The order is guaranteed; subclasses fill named blanks.
Decorator wraps from outside; a template method opens holes from inside.
07
Quick check
🧠 Quick check
Why should the template method itself be final (or non-virtual)?
The whole value of the pattern is that the order is not negotiable. If a subclass can override run(), then a new job can once again forget to release the lock, record metrics in the wrong place or skip the alert — and you have three subtly different pipelines with extra inheritance on top. Sealing the template turns "everyone remembers to do this" from a code-review convention into something the compiler enforces. (The JIT can indeed inline final methods, but that isn't why you do it — and note the honest caveat: Python's @final is only checked by a type checker, and TypeScript has no final at all, so in those languages this is a convention you have to defend in review.)
In the wild
JavaHttpServlet.service() inspecting the verb and calling doGet/doPost; AbstractList, where implementing get and size gets you the whole List API.
Pythonunittest.TestCase — run() calls setUp, your test_*, then tearDown, in an order you don't control.
Gosort.Sort — the algorithm is fixed and you supply Len, Less and Swap through sort.Interface.
JavaScriptReact class components: you write render() and componentDidMount(), React decides when they run — the Hollywood Principle in its most-used form.
C#The ASP.NET page lifecycle and ControllerBase filters; Stream, where overriding Read and Write gives you the rest of the API.
C++The non-virtual interface idiom throughout the standard library and Qt — for example QAbstractItemModel, where you implement rowCount and data and the view drives everything else.
What is the difference between Template Method and Strategy?
Both let part of an algorithm vary; the mechanism and the timing differ. Template Method uses inheritance: the base class owns the sequence and a subclass supplies steps, chosen when you pick the class — so the variation is fixed at compile time and a subclass gets only the holes the base class chose to open. Strategy uses composition: the whole algorithm is an object you can swap at runtime, and one object can use several strategies. If you need the order guaranteed, Template Method; if you need the algorithm interchangeable, Strategy.
What is the Hollywood Principle?
"Don't call us, we'll call you" — your code doesn't drive the framework, the framework calls your code at points it decides. Template Method is the pattern that implements it: you supply fetch(), setUp() or render(), and something else chooses when they run. It's also the practical dividing line between a library (you call it) and a framework (it calls you), and it explains the pattern's characteristic frustration — you can only customise at the points the base class offered.
Should the template method be final?
Yes, wherever your language can express it. The sequence is the guarantee you're buying — if a subclass can override the template, it can skip the lock, the metrics or the error handling, and you're back to the duplicated pipelines you set out to fix. Java uses final, C# is non-virtual by default, and C++ has the non-virtual interface idiom where the public method isn't virtual at all and the steps are private virtuals. In Python @final is checked only by type checkers, and TypeScript has no final — there, it's a convention you enforce in review.
What is a hook method?
A step in the template with an empty (or trivially useful) default implementation that subclasses may override but usually don't — beforeWrite(), shouldRetry(). Abstract steps are mandatory; hooks are optional. They're genuinely useful, but they're also how this pattern rots: a base class carrying a dozen empty beforeX/afterY methods has become impossible to follow. Add a hook when a real subclass needs it, never in anticipation.
How do you use Template Method in a language without inheritance, like Go?
Invert it. The skeleton becomes a plain function, and the varying steps become an interface (or a struct of function values) that the function takes as an argument — Run(ctx, src Source). Optional steps become optional interfaces you check with a type assertion, which is Go's equivalent of an overridable hook. You keep the real guarantee, since callers can't reorder steps they never see, and you avoid the fragile base class entirely. It's worth borrowing this shape in languages that do have inheritance, for the same reasons.
Finish the handbook, earn the certificate
All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.