Several classes implement near-identical algorithms that differ only in a few specific steps. The shape is duplicated in every class; the shared structure is convention rather than code; adding a step to the algorithm shape means editing every class in lockstep.
The base class owns the algorithm's skeleton as a concrete method (the template method). Each variable step is an overridable method (hook). Subclasses fill in only the steps that differ; the shared skeleton lives in one place.
Before the pattern
class JavaScriptBuilder {build() {this.fetchSource();this.compile();this.runTests();this.package();}fetchSource() { return git.clone(this.repo); }compile() { return run('npx tsc'); }runTests() { return run('npm test'); }package() { return run('npm pack'); }}class PythonBuilder {build() {this.fetchSource();this.compile();this.runTests();this.package();}fetchSource() { return git.clone(this.repo); }compile() { return run('python -m py_compile .'); }runTests() { return run('pytest'); }package() { return run('python -m build'); }}class GoBuilder {build() {this.fetchSource();this.compile();this.runTests();this.package();}fetchSource() { return git.clone(this.repo); }compile() { return run('go build ./...'); }runTests() { return run('go test ./...'); }package() { return run('tar -czf project.tar.gz .'); }}// build() skeleton + fetchSource() duplicated across every builder.// Adding a 'lint' step in the pipeline = 3 edits in lockstep.
After the pattern
class Builder {build() {this.fetchSource();this.compile();this.runTests();this.package();}fetchSource() {return git.clone(this.repo);}compile() {throw new Error('subclass implements compile');}runTests() {throw new Error('subclass implements runTests');}package() {throw new Error('subclass implements package');}}class JavaScriptBuilder extends Builder {compile() { return run('npx tsc'); }runTests() { return run('npm test'); }package() { return run('npm pack'); }}class PythonBuilder extends Builder {compile() { return run('python -m py_compile .'); }runTests() { return run('pytest'); }package() { return run('python -m build'); }}class GoBuilder extends Builder {compile() { return run('go build ./...'); }runTests() { return run('go test ./...'); }package() { return run('tar -czf project.tar.gz .'); }}
Duplicated algorithm skeletons are the worst form of Duplicated Code — the duplication is in shape, not just in code text. Adding a new pipeline step (e.g., 'lint') is Shotgun Surgery; one missed subclass produces a pipeline that's silently incomplete.
Inheritance couples subclasses tightly to the base class's skeleton; subclasses cannot easily reorder, skip, or insert steps without breaking the contract. The base class's hook set is the spec; subclasses cannot evolve independently without coordinated edits.
Skeleton lives in one place; new steps land as a single edit; subclasses are small and read as 'here are the variable bits.' The algorithm shape becomes a structural commitment, not a convention; the type system can enforce hook implementations on every subclass.
When the template method's skeleton stops describing what every subclass actually does — when subclasses need conditional skipping, reordering, or alternate paths — inheritance becomes a straitjacket. At that point reach for Strategy or a builder-style pipeline that constructs the algorithm shape from composable parts rather than inheriting it.