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

Problem

Several workflows share the same sequence but duplicate orchestration because a few steps differ by case.

Rule

Keep the invariant algorithm in one template method and expose only intentional variation points to specialized implementations.

Run
  1. TEMPLATE executes the shared sequence in a controlled order.
  2. FIXED STEPS enforce invariants that every workflow must preserve.
  3. HOOKS let specialized implementations vary selected operations only.
Output

Related workflows reuse one verified sequence while customizing only the parts designed to change.

Template Method pattern example in TypeScript

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); }
}
Validation and storage are written once and cannot be reordered by a variant. CSV overrides parsing plus the optional normalize hook; JSON overrides parsing alone.

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.

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.