Template Method pattern
Open in the interactive app: practice, quiz, mock interview →In one minute
- Template Method puts the fixed outline of an algorithm in one method of a base class and leaves named steps for subclasses to fill in. The base class calls the subclass, not the other way round: "don't call us, we'll call you".
- Steps come in two kinds: abstract operations that every subclass must provide, and hooks with a sensible default (often doing nothing) that a subclass may override.
- Its real value is invariants. The skeleton is where "teardown always runs after a successful setup", "the lock is released", and "the request is logged" live, so no subclass can forget them.
- It couples through inheritance, so it fits frameworks that own the control flow (test runners, request handlers, collection base classes). In application code, Strategy or plain function parameters give the same variation with less coupling. Go has no inheritance at all and expresses it as a function over an interface.
- Agents copy a 40-line
run()into each new job class and tweak two lines. Review asks for one skeleton with named steps, and checks that the steps cannot skip the invariants.
How it works
The problem
A data platform runs nightly batch jobs: import invoices, recompute loyalty points, expire coupons. Each job class was written by a different engineer, and each has its own run(): open a database connection, loop over items, handle errors, close the connection, write a summary. They drifted. The coupon job closes its connection only on success, so a failure leaks it. The invoice job swallows every error. The loyalty job counts skipped items as processed. Every new job copies one of the three and inherits its bugs.
The jobs differ in two or three places: how an item is processed, which items are skipped, and whether one bad item stops the run. Everything else, the order of steps, the error accounting, the guarantee that cleanup happens, should be written once.
The skeleton and its steps
Template Method moves the outline into a base class method and turns each varying part into a named method call:
class BatchJob:
def run(self, items): # the template method
self.setup()
try:
for item in items:
if self.should_skip(item): # hook, default False
continue
self.process(item) # abstract step
finally:
self.teardown() # hook, default no-op
def should_skip(self, item): return False
def setup(self): pass
def teardown(self): passA coupon job now overrides process and perhaps should_skip, and nothing else. It cannot leak the connection, because it never writes the finally. Mark the template method so subclasses do not override it: final in Java, a non-virtual function in C++, typing.final in Python (enforced by type checkers, not at runtime).
Hooks versus abstract steps
Abstract steps are the parts with no sensible default; leaving one out should fail loudly, when the subclass is instantiated (abc.abstractmethod) or at compile time. Hooks are extension points with a safe default, so most subclasses ignore them. Keep the number of steps small. A skeleton with twelve hooks is a framework nobody can reason about, because every subclass is a different program. Name hooks for when they run (before_item, on_error) and document whether their return value changes control flow.
Where you already use it
- Python's
unittest.TestCase.runcallssetUp, then the test method, thentearDownonly ifsetUpsucceeded, thendoCleanupsregardless. Each test class overrides steps and never writes the sequence. - Python's
socketserver.BaseRequestHandler.__init__callssetup(), thenhandle()inside atry, withfinish()in thefinally. - Java collections.
AbstractListasks a subclass forget(int)andsize()and builds iteration,indexOf,contains,equalsandhashCodeon top. - Servlets.
HttpServlet.service()reads the method and dispatches todoGet,doPostand the rest, which subclasses override. - Go's
sort.Sort. The skeleton is the sorting algorithm; the steps areLen,LessandSwapfromsort.Interface. There is no base class, only a function over an interface.
The composition version
Template Method and Strategy solve the same problem with different tools (see Strategy). Template Method varies steps by subclassing; Strategy passes the varying part in as an object. Spring's JdbcTemplate is named after the pattern but uses callbacks: the template owns connection, statement and exception translation, and you pass a RowMapper. Callbacks let one class combine steps from different sources and change them at runtime; inheritance gives one fixed combination per subclass. The rule of thumb: inheritance when a framework owns the lifecycle and steps share state on self; composition everywhere else.
Go: embedding is not inheritance
Engineers from Java often try this in Go: a BaseJob struct with a Run method that calls j.Process(item), embedded in CouponJob, which defines its own Process. It compiles, and Run calls BaseJob.Process, not CouponJob.Process, because a promoted method's receiver is the embedded value and Go has no virtual dispatch on structs. The idiomatic version is a function Run(job Steps, items) that takes an interface for the required steps. Optional hooks become small extra interfaces checked with a type assertion, the same trick io.Copy uses when it checks for WriterTo and ReaderFrom.
Reviewing agent-written code
Asked to "add a job that expires coupons", an agent will usually find the nearest job, copy its whole class, and change the body of the loop. The diff looks small and the bug surface doubles. When a base class exists, agents also override the template method itself to "add one thing", which silently drops the invariants. Point the agent at the skeleton, list the steps it may override, and in review check three things: the template method is not overridden; subclasses do not call each other's steps out of order; and the skeleton's guarantees (cleanup, accounting, error policy) are covered by tests written against the base class with a fake subclass.
The same shape elsewhere
Template Method is multi-step consistency inside one process: a fixed sequence where some steps must always run once others have. The same shape governs a database transaction (begin, work, then exactly one of commit or rollback), a CI pipeline (checkout, build, test, then upload artifacts and report status even on failure), and Kubernetes pod lifecycle hooks (postStart and preStop around a container the platform runs). In every case the platform owns the order and the user supplies the middle.
Down the stack
Layer 1The contract
The template method promises an order and a set of guarantees: which steps run, in what sequence, which run on failure, and what happens to errors. For the batch job: setup runs first; if it fails, nothing else runs and the run is reported as aborted; once it succeeds, teardown runs exactly once whatever happens; each item is skipped, processed or failed, never two of these. Subclasses promise that each step does only its own job and does not call the template method again (re-entrancy is usually unsupported). Hooks promise that their defaults preserve the base behaviour, so a subclass that overrides nothing still behaves correctly.
Layer 2The mechanism
In class-based languages the skeleton calls steps through virtual dispatch. In C++ and Java, each object carries a pointer to its class's virtual method table, and this->process(item) loads the function pointer from a fixed slot. Herb Sutter's non-virtual interface idiom makes this explicit: the public method is non-virtual and owns the invariants, the customisation points are private virtual functions. Python looks steps up through the class's method resolution order on each call (type(self).__mro__), cached per type with a version tag that is invalidated when a class is modified. Java's AbstractList shows the cost of a poor primitive choice: its iterator calls get(i) per element, which is O(1) for an array but O(n) for a linked structure, so AbstractSequentialList exists to flip the primitive to a listIterator.
Go's mechanism is the interface: sort.Sort(data) calls data.Less(i, j) and data.Swap(i, j) through the itab, about n log n times each. The generic slices.Sort takes the same skeleton and lets the compiler specialise comparisons for the element type, which removes those interface calls; since Go 1.22, sort.Ints and friends call into slices.Sort for that reason.
Layer 3The machine
A virtual call costs a dependent load of the vtable pointer, a load of the function pointer, and an indirect branch: around 1 to 3 ns when predicted, and 10 to 20 ns when the predictor misses. The bigger cost is lost inlining. If less is a two-instruction integer compare, calling it indirectly 20 million times while sorting a million items adds tens of milliseconds and blocks vectorisation. JITs recover this: HotSpot uses class hierarchy analysis and call-site profiles to inline a step when only one subclass is loaded or seen, guarded by a cheap type check, and deoptimises if a new subclass appears. C++ compilers devirtualise when the class or method is marked final. In CPython, a method call on self costs roughly 50 to 100 ns, so a step called per item in a million-item batch adds a fraction of a second, which matters only when the step itself is trivial.
The skeleton is also where resource lifetime is enforced at the machine level. The finally that calls teardown is what closes the file descriptor or returns the pooled connection. A job that leaks one connection per failed run exhausts a pool of 20 after 20 failures, a common way that "the batch is down" turns into "the API is down" when both share a database.
Why it's built this way
- Many tasks share an outline and differ in a few steps, and copies of the outline drift.
- The outline holds invariants (cleanup, accounting, ordering) that must hold for every variant, so it should exist once and not be overridable.
- The varying steps must be called by the outline, so the outline needs an indirect call: a virtual method, an interface, or a callback.
- Inheritance makes those calls cheap to write when the framework owns the object's lifecycle, which is why frameworks use it.
- Inheritance also couples subclass to base and fixes the combination at class definition, so outside frameworks the same idea is expressed with composition.
Diagram
sequenceDiagram
participant C as Caller
participant T as BatchJob.run
participant S as Subclass steps
C->>T: run(items)
T->>S: setup()
loop each item
T->>S: should_skip(item)
T->>S: process(item)
T--)S: on_error(item, err) if it failed
end
T->>S: teardown()
T-->>C: ReportWorked example
A payments company had 31 nightly jobs, each with its own copy of the run loop. An outage review found that 9 of them closed their database connection only on success. On the night a schema migration made 7 of those jobs fail, they leaked 7 connections each on retry (5 retries, 35 connections), exhausting the 40-connection pool the jobs shared with the merchant dashboard API, which returned errors for 26 minutes.
The fix introduced a BatchJob base class whose run is final and owns setup, teardown in a finally, error policy and a structured report (processed, skipped, failed, aborted). The 31 jobs were migrated over three weeks: on average each lost 38 lines and kept 12, the code that actually differed. A test suite for the base class uses a recording subclass to assert the order of calls, including "setup fails, teardown not called" and "process raises, teardown still called". Connection leaks from batch jobs have not recurred, and the dashboard and batch pools were split as defence in depth.
The overhead of the extra method calls was measured on the largest job, 4.2 million items: about 0.4 s of a 38-minute run, under 0.02%.
Trade-offs
| Option | Strengths | Weaknesses | Choose when |
|---|---|---|---|
| Copy the loop per job | No abstraction to learn | Invariants drift; bugs multiply | One job, or truly different outlines |
| Template Method (base class) | Invariants written once; steps share state on self | Inheritance coupling; one combination per subclass | A framework owns the lifecycle |
| Strategy or callbacks | Steps mix and change at runtime; easy to test alone | More wiring; steps cannot share self state | Application code; several independent variation points |
| Function over an interface (Go) | No inheritance; optional hooks by type assertion | Hooks less discoverable | Go, or any language favouring composition |
Context manager or defer | Lightweight lifecycle for one resource | Covers setup and cleanup, not a multi-step outline | Only the "always clean up" part is needed |
Pitfalls in production
- Subclasses overriding the template method to add one line, which drops the base invariants. Fix: mark it final (or
typing.final), and add the needed extension point as a hook. - The "call super" trap, where a subclass overrides
setupand forgets to call the basesetup. Fix: give the base class its own private setup that always runs, and a separate empty hook for subclasses. - Too many hooks, so each subclass is effectively a different algorithm. Fix: split the base class by purpose, or move independent variations into strategies.
- A step that calls back into the template method, causing recursion or double cleanup. Fix: document steps as non-re-entrant and guard with a state flag.
- Deep hierarchies (
BaseJob,DbJob,PaymentsDbJob,NightlyPaymentsDbJob), where nobody knows which override wins. Fix: keep one level of template, compose the rest. - Go structs that embed a base and expect virtual dispatch, so the base's step runs instead of the outer one. Fix: a skeleton function over an interface.
Leadership lens
- Ask what the invariants are before approving a base class. A template method earns its place by guaranteeing something (cleanup, auditing, error accounting) that copies got wrong.
- Own the skeleton as a platform: one team maintains the base class, its tests and its changelog; job authors own only steps. Changing the skeleton is a change to every job and gets reviewed that way.
- Prefer composition for new application code and reserve inheritance for framework boundaries; this keeps the codebase testable and the hierarchy shallow.
- Give agents the skeleton as the rule: "new job = subclass
BatchJob, overrideprocessand at most these hooks". It turns a copy-paste change into a small, reviewable one.
Check yourself
Why does
unittestskiptearDownwhensetUpraises, but still run cleanups registered withaddCleanup?Show answer
tearDownmay assume everythingsetUpcreated exists, so running it after a partial setup can fail confusingly; cleanups are registered as each resource is created, so they release exactly what was acquired.In Go, a
BaseJobwith methodRuncallingb.Process()is embedded inCouponJob, which definesProcess. WhichProcessruns, and how do you fix the design?Show answer
BaseJob.Process, because promoted methods have the embedded value as receiver and structs have no virtual dispatch. WriteRun(job Steps, items)as a function over an interface thatCouponJobimplements.When would you replace a template method with Strategy?
Show answer
When variations are independent and should be mixed (for example skip policy and error policy chosen separately), changed at runtime, or tested alone; inheritance gives one fixed combination per subclass.
Why can a JIT make a template method's step calls nearly free while CPython cannot?
Show answer
The JIT sees which subclass actually reaches a call site and inlines that method behind a type guard; CPython looks up and calls the method dynamically each time.
Watch
- Template Method Pattern, Design Patterns ep 13 (Christopher Okhravi): the pattern on a whiteboard, including hooks and its relation to Strategy.
- Template Method Design Pattern (Derek Banas): a short Java implementation with a hook method.
Free resources
- Template Method (Refactoring.Guru): the structure, hooks, and comparison with Strategy and Factory Method.
- Virtuality (Herb Sutter): the non-virtual interface idiom and why customisation points should be private.
- Call Super (Martin Fowler): why requiring subclasses to call the parent method is fragile, and the template method alternative.
- unittest (Python docs):
setUp,tearDownandaddCleanupas a template method you use every day. - sort package source (Go): a skeleton algorithm calling steps through
sort.Interface, with no inheritance.