Template Method: Freeze the Sequence, Vary the Steps
Template Method defines a stable algorithm sequence while allowing selected steps to vary through subclass hooks or overrides.
What is the Template Method pattern?
The Template Method pattern is a behavioral design pattern where a base class defines the skeleton of an algorithm as a sequence of steps, implements the invariant steps itself, and leaves selected steps abstract (or overridable) for subclasses to fill in. The base class calls the subclass — the "Hollywood principle": don't call us, we'll call you.
It is the pattern for "these workflows are 90% identical." The shared sequence — validate, parse, normalize, store — is written and tested once, and each variant customizes only the step designed to vary. Testing frameworks (setup/test/teardown) and framework lifecycle hooks are template methods you use daily.
The logic, step by step
Several workflows share the same sequence but duplicate orchestration because a few steps differ by case.
Keep the invariant algorithm in one template method and expose only intentional variation points to specialized implementations.
- TEMPLATE executes the shared sequence in a controlled order.
- FIXED STEPS enforce invariants that every workflow must preserve.
- HOOKS let specialized implementations vary selected operations only.
Related workflows reuse one verified sequence while customizing only the parts designed to change.
Template Method pattern example in TypeScript
abstract class ImportPipeline<Row> {
// The template method: the sequence is fixed and final.
run(raw: string): ImportReport {
this.validate(raw);
const rows = this.parse(raw); // varies per format
const clean = rows.map((r) => this.normalize(r));
return this.store(clean);
}
private validate(raw: string): void {
if (raw.trim().length === 0) throw new Error('Empty import file');
}
protected abstract parse(raw: string): Row[];
protected normalize(row: Row): Row { return row; } // hook: optional override
private store(rows: Row[]): ImportReport {
db.insertMany(rows);
return { imported: rows.length };
}
}
class CsvImport extends ImportPipeline<UserRow> {
protected parse(raw: string): UserRow[] { return csv.parse(raw); }
protected normalize(row: UserRow): UserRow {
return { ...row, email: row.email.toLowerCase() };
}
}
class JsonImport extends ImportPipeline<UserRow> {
protected parse(raw: string): UserRow[] { return JSON.parse(raw); }
} Run it in the real world
A data import pipeline always validates, parses, normalizes, and stores, while CSV and JSON importers override only parsing.
When to use it — and when not to
Reach for Template Method when
- Several workflows share an identical sequence and differ in one or two steps.
- The order of steps is an invariant you want the compiler to defend — variants must not skip validation.
- You are building a framework and want user code to plug into defined extension points.
Skip it when
- Steps need to vary at runtime or per call — that is Strategy territory, composition beats inheritance there.
- Hooks have multiplied until reading the true control flow requires jumping through five classes.
Template Method vs related patterns
Strategy pattern — Template Method fixes the skeleton at compile time via inheritance; Strategy swaps whole behaviors at runtime via composition. If a variant needs to change per request, migrate the hook to an injected strategy.
Factory Method pattern — Factory Method is a Template Method whose single varying step happens to be "create the object." The patterns share their skeleton-plus-override mechanics.
Frequently asked questions
What is the Hollywood principle in the Template Method pattern?
"Don't call us, we'll call you" — the base class controls the flow and invokes subclass hooks at the right moments, instead of subclasses calling shared helpers whenever they please. Inversion of control is what keeps the sequence trustworthy.
What is the difference between a hook and an abstract method here?
An abstract method is a step the subclass must implement (parsing, in the example). A hook has a sensible default — often doing nothing — that subclasses may override. Hooks keep simple variants short.
Template Method or higher-order functions?
In functional codebases, passing step functions into a pipeline function achieves the same guarantee without inheritance. Choose Template Method when variants share state and helpers worth grouping in a class; choose function composition when steps are pure.