In real projects, the interface you want and the interface you actually receive are often different. This happens especially with external libraries, legacy systems, and third-party APIs. The adapter pattern is a classic way to connect those mismatched shapes without rewriting everything.
In this post, we will cover:
- what the adapter pattern is
- why it is useful
- when it appears often
- how it differs from a simple wrapper
The key idea is that the adapter pattern converts an incompatible interface into the form the caller expects, reducing the need to change the rest of the system.
What is the adapter pattern?
The adapter pattern keeps the original implementation in place and adds a conversion layer in front of it so that the rest of the code can see a more suitable interface.
In simple terms:
- keep the real implementation
- wrap it in a shape the caller expects
- connect the two sides
Why is it useful?
If you respond to every interface mismatch by rewriting all callers:
- change impact grows
- external detail spreads everywhere
- testing and maintenance get harder
An adapter centralizes the translation responsibility.
When does it fit well?
- integrating external libraries
- connecting to legacy APIs
- bridging old and new code
- fitting external tools into an internal standard shape
So it is especially useful when the real need is connection, not a total rewrite.
How is it different from a wrapper?
They overlap, but the adapter pattern is especially focused on making two mismatched interfaces fit together.
So compared with:
- simple packaging
- add-on behavior
the core emphasis is interface translation.
Common misunderstandings
1. If interfaces differ, just change the callers
That may work in small cases, but once the impact grows, an adapter is often safer.
2. An adapter is just a temporary patch
A well-designed adapter can become a very healthy boundary that isolates change.
3. Adapter and decorator are basically the same
Both can wrap objects, but adapter is about translation while decorator is more about behavior extension.
Simple Example
class LegacyMailer {
sendMail(text: string): void {
console.log(`legacy mail: ${text}`);
}
}
interface Notifier {
send(message: string): void;
}
class MailAdapter implements Notifier {
constructor(private legacyMailer: LegacyMailer) {}
send(message: string): void {
this.legacyMailer.sendMail(message);
}
}
class NotificationService {
constructor(private notifier: Notifier) {}
sendWelcome(): void {
this.notifier.send('Welcome!');
}
}
const service = new NotificationService(new MailAdapter(new LegacyMailer()));
service.sendWelcome();
The main code expects Notifier, and the adapter translates that expectation into the legacy sendMail interface.
FAQ
Q. Where should beginners look for adapter opportunities?
Start where a library or legacy interface feels awkward compared with the style of the surrounding code.
Q. Does adapter help testing too?
Yes. It can isolate external details and make replacement easier during tests.
Q. Can too many adapters become messy?
They can, which is why they work best when placed at meaningful boundaries.
Read Next
- For interface and abstraction choices, continue with the Interface vs Abstract Class Guide.
- For add-on behavior through wrapping, compare it with the Decorator Pattern Guide.
While AdSense review is pending, related guides are shown instead of ads.
Start Here
Continue with the core guides that pull steady search traffic.
- Middleware Troubleshooting Guide: Redis vs RabbitMQ vs Kafka A practical middleware troubleshooting guide for developers covering when to reach for Redis, RabbitMQ, or Kafka symptoms first, and which problem patterns usually belong to each tool.
- Kubernetes CrashLoopBackOff: What to Check First A practical Kubernetes CrashLoopBackOff troubleshooting guide covering startup failures, probe issues, config mistakes, and what to inspect first.
- Kafka Consumer Lag Increasing: Troubleshooting Guide A practical Kafka consumer lag troubleshooting guide covering what lag usually means, which consumer metrics to check first, and how poll timing, processing speed, and fetch patterns affect lag.
- Kafka Rebalancing Too Often: Common Causes and Fixes A practical Kafka troubleshooting guide covering why consumer groups rebalance too often, what poll timing and group protocol settings matter, and how to stop rebalances from interrupting useful work.
- Docker Container Keeps Restarting: What to Check First A practical Docker restart-loop troubleshooting guide covering exit codes, command failures, environment mistakes, health checks, and what to inspect first.
While AdSense review is pending, related guides are shown instead of ads.