Two or more classes follow the same algorithm outline — same order of steps, same control flow — but each open-codes the whole sequence inline. The structural similarity is invisible: every change to the algorithm risks landing in one class but not the others.
The algorithm lives once in a superclass Template Method that calls named primitive operations. Subclasses implement only the primitives; algorithmic edits land in one place and apply uniformly.
Before the refactoring
// Two reports follow the same outline but each open-codes the whole algorithm.class HtmlReport {generate(data) {let output = '<html><body>';output += `<h1>${data.title}</h1>`;output += '<table>';for (const row of data.rows) {output += `<tr><td>${row.name}</td><td>${row.value}</td></tr>`;}output += '</table>';output += '</body></html>';return output;}}class TextReport {generate(data) {let output = '';output += data.title.toUpperCase() + '\n';output += '='.repeat(data.title.length) + '\n';for (const row of data.rows) {output += row.name + ': ' + row.value + '\n';}return output;}}
After the refactoring
// The algorithm lives once in Report; subclasses supply the per-format primitives.class Report {generate(data) {let output = this.openSection();output += this.formatTitle(data.title);output += this.openRows();for (const row of data.rows) {output += this.formatRow(row);}output += this.closeRows();output += this.closeSection();return output;}}class HtmlReport extends Report {openSection() { return '<html><body>'; }closeSection() { return '</body></html>'; }formatTitle(t) { return `<h1>${t}</h1>`; }openRows() { return '<table>'; }closeRows() { return '</table>'; }formatRow(row) { return `<tr><td>${row.name}</td><td>${row.value}</td></tr>`; }}class TextReport extends Report {openSection() { return ''; }closeSection() { return ''; }formatTitle(t) { return t.toUpperCase() + '\n' + '='.repeat(t.length) + '\n'; }openRows() { return ''; }closeRows() { return ''; }formatRow(row) { return row.name + ': ' + row.value + '\n'; }}
When the outline is duplicated across classes, reordering steps, adding a new step, or fixing a control-flow bug requires N synchronized edits. Reviewers must scan all N implementations to confirm the algorithm stayed consistent; subtle drift between siblings is the dominant defect.
Template Method couples subclasses to the superclass's algorithm shape; subclasses cannot reorder steps or skip them without breaking the contract. Primitives proliferate as the algorithm grows, and each must be implemented in every subclass — adding a new step is a Shotgun Surgery move down the hierarchy.
Algorithmic invariants live in one place; subclasses focus on what's distinctive about them. Adding a new format is a new subclass implementing the primitives; the algorithm's correctness is verified once and inherited.
Template Method calcifies the algorithm shape early. When one subclass needs a fundamentally different ordering, the superclass's template no longer fits, and the subclass either fights the framework with hooks/flags or escapes via composition. Strategy is often the better answer when the algorithm itself varies.