Adapter Pattern: Translate at the Boundary
Adapter converts an existing interface into the interface a client expects, containing incompatibility at one explicit system boundary.
What is the Adapter pattern?
The Adapter pattern is a structural design pattern that lets objects with incompatible interfaces work together. An adapter implements the interface your code expects, wraps the object that has the wrong shape, and translates every call between the two — in both directions when needed.
Its real value is architectural: it pins incompatibility to one file. Without an adapter, a vendor's payload shape seeps into every caller, and swapping vendors means an archaeology project. With adapters, your domain defines the contract it wants, each vendor gets one translation layer, and replacing Stripe with a bank gateway means writing one new adapter — not rewriting checkout.
The logic, step by step
A useful library or external service exposes the wrong shape, forcing translation code into every caller.
Place one adapter between the client contract and the incompatible dependency, translating inputs and outputs in both directions.
- CLIENT sends a request using the interface it already understands.
- ADAPTER translates the request into the dependency-specific format.
- DEPENDENCY responds and the adapter maps the result back again.
The core remains stable while vendor-specific or legacy differences stay isolated at the edge.
Adapter pattern example in TypeScript
// The contract YOUR domain wants:
interface PaymentGateway {
charge(amountCents: number, currency: string, token: string): Promise<ChargeResult>;
}
// The vendor SDK you actually have (wrong shape, wrong units):
class StripeClient {
async createPaymentIntent(opts: {
amount: number; currency: string; payment_method: string; confirm: boolean;
}): Promise<{ id: string; status: 'succeeded' | 'requires_action' }> { /* ... */ }
}
class StripeAdapter implements PaymentGateway {
constructor(private stripe: StripeClient) {}
async charge(amountCents: number, currency: string, token: string): Promise<ChargeResult> {
const intent = await this.stripe.createPaymentIntent({
amount: amountCents,
currency: currency.toLowerCase(), // vendor quirk stays here
payment_method: token,
confirm: true,
});
return { ok: intent.status === 'succeeded', reference: intent.id };
}
}
// Checkout depends on PaymentGateway only — Stripe is a config detail.
const gateway: PaymentGateway = new StripeAdapter(new StripeClient()); Run it in the real world
A payment domain uses one Money contract while adapters translate it to Stripe, PayPal, or a bank gateway without leaking vendor payloads inward.
When to use it — and when not to
Reach for Adapter when
- A third-party SDK, legacy module, or external API exposes the wrong interface for your domain.
- You integrate several providers of the same capability and want callers to see one contract.
- Migrating between dependencies incrementally — the adapter lets old and new coexist behind one interface.
Skip it when
- You control both sides — change the interface at the source instead of translating forever.
- The "adapter" starts adding business rules; translation layers must convert shape, not meaning.
Adapter vs related patterns
Decorator pattern — Both wrap an object, but Decorator preserves the interface and adds behavior, while Adapter changes the interface and adds nothing. Same mechanics, opposite purpose.
Facade pattern — Adapter reshapes one incompatible thing to fit an existing contract; Facade invents a new, simpler contract over many things. Adapter fits, Facade simplifies.
Frequently asked questions
What is a real-life example of the Adapter pattern?
A travel power plug adapter: your laptop plug (existing interface) does not fit the wall socket (expected interface), and the adapter translates between them without modifying either. In code, wrapping a vendor SDK so it satisfies your own domain interface is the same move.
What is the difference between a class adapter and an object adapter?
A class adapter uses inheritance — it extends the incompatible class and implements the target interface, which requires multiple inheritance or interface tricks. An object adapter uses composition, holding the incompatible object inside. Object adapters are more flexible and are what you should default to.
Where should adapters live in a hexagonal or clean architecture?
At the edges. The domain defines ports (the interfaces it needs), and adapters implement those ports in the infrastructure layer. Adapter is the pattern that hexagonal architecture industrializes.