6 min read

Template Method Design Pattern
The Template Method pattern is a behavioural design pattern where a base class defines the skeleton of an algorithm, and subclasses fill in the variable parts without changing the overall structure.
The pattern shows up often in library and framework code. Once you know what to look for, you start seeing it everywhere.
The problem it solves
Imagine you are building a client that talks to multiple AI providers: OpenAI, Groq, Mistral, DeepSeek. Each one has its own API endpoint and a different default model name. But the mechanics of building an HTTP client, injecting an API key, and sending a request are identical across all of them.
A naive approach duplicates that shared logic in every driver class. When you need to change how the HTTP client is configured, you update it in six places and hope you don't miss one.
The Template Method pattern solves this by putting the shared logic in one place and making subclasses responsible only for the parts that genuinely differ.
The pattern
Define an abstract base class with a concrete method that orchestrates the algorithm. That method calls one or more abstract (or overridable) methods. Subclasses implement those smaller methods to provide the driver-specific details.
The concrete method is the template method. It controls the flow. Subclasses cannot change the flow, only the inputs.
PHP example: AI provider drivers
Here is the abstract base class. The send() method is the template method. It handles the HTTP plumbing and delegates to two hooks that subclasses must implement.
<?php abstract class OpenAICompatibleDriver { abstract protected function resolveBaseUrl(): string; abstract protected function resolveDefaultModel(): string; public function isOpenAICompatible(): bool { return true; } public function send(string $prompt, ?string $model = null): string { $model ??= $this->resolveDefaultModel(); $baseUrl = $this->resolveBaseUrl(); $response = \Illuminate\Support\Facades\Http::withToken(config('services.ai.key')) ->baseUrl($baseUrl) ->post('/chat/completions', [ 'model' => $model, 'messages' => [ ['role' => 'user', 'content' => $prompt], ], ]); return $response->json('choices.0.message.content', ''); } }
Each concrete driver only needs to declare two things:
final class GroqDriver extends OpenAICompatibleDriver { protected function resolveBaseUrl(): string { return 'https://api.groq.com/openai/v1'; } protected function resolveDefaultModel(): string { return 'llama3-8b-8192'; } } final class MistralDriver extends OpenAICompatibleDriver { protected function resolveBaseUrl(): string { return 'https://api.mistral.ai/v1'; } protected function resolveDefaultModel(): string { return 'mistral-small-latest'; } } final class DeepSeekDriver extends OpenAICompatibleDriver { protected function resolveBaseUrl(): string { return 'https://api.deepseek.com/v1'; } protected function resolveDefaultModel(): string { return 'deepseek-chat'; } }
Adding a new provider is now a three-minute job. You create a class, extend the base, override the two methods. The HTTP setup, error handling, and authentication logic stay untouched.
How it differs from Strategy
The Strategy pattern and the Template Method pattern both deal with varying behaviour, but they work differently.
With Strategy, you inject a collaborator. The algorithm is replaced entirely at runtime by swapping in a different object that conforms to a shared interface. The context class knows nothing about how the algorithm works.
With Template Method, inheritance is the mechanism. The algorithm lives in the base class. Subclasses extend it, not replace it. The flow is fixed; only specific steps can be overridden.
Use Strategy when callers need to swap behaviour at runtime or compose algorithms from the outside. Use Template Method when you have a fixed process and want subclasses to fill in specific blanks.
When to use the Template Method pattern
It fits well when:
- Multiple classes share the same algorithm skeleton but differ in specific steps.
- You want to enforce a fixed process (authentication before request, validation before save) while letting subclasses customise parts of it.
- You are building a framework or library and want to give extension points without exposing internals.
- Duplication is accumulating across classes that do the same thing in slightly different ways.
It is less suitable when the variation is complex or when you need to combine behaviours from multiple sources. In those cases, composition and Strategy tend to scale better.
Laravel uses this pattern
Laravel's Illuminate\Database\Eloquent\Model uses Template Method in a few places.
The boot() method is called once per model class when it is first used. The base Model class has a concrete boot() implementation that registers default events and behaviours. Individual models can override it to add their own setup:
final class Post extends Model { protected static function boot(): void { parent::boot(); static::creating(function (Post $post) { $post->slug = str($post->title)->slug(); }); } }
The base class defines when boot() runs and what happens before and after. Your model only decides what extra work belongs inside it.
The newQuery() method follows the same idea. The base class defines the algorithm for building an Eloquent query builder. You can override it on a specific model to swap in a custom query builder class, and everything else (scopes, eager loading, pagination) continues to work as normal.
final class Post extends Model { public function newEloquentBuilder($query): PostQueryBuilder { return new PostQueryBuilder($query); } }
Laravel uses this pattern in many places: filesystem drivers, cache stores, queue connectors. The framework defines the contract and the flow; you provide the specifics.
Pitfalls to watch for
Inheritance is a tight coupling. When you extend a base class, you are depending on its internals. If the base class changes, every subclass is affected. Keep base classes stable and focused.
Avoid putting too much logic in the template method. If it grows to twenty steps, subclasses become hard to reason about. When that happens, consider splitting the base class or switching to a different approach.
Also avoid making too many methods abstract. If every step is a hook, you push complexity into subclasses and lose the benefit of shared behaviour. The base class should handle the parts that never vary.
Conclusion
The Template Method pattern fits when you have a process that is mostly the same but varies in specific ways. Put the shared logic in a base class, expose the variable parts as methods, and let subclasses fill them in.
It is not always the right tool. For runtime variation or complex composition, Strategy or decorator patterns often serve better. But for a case like the AI provider drivers above, it removes duplication without much ceremony.