Overview
The Adapter pattern converts the interface of an existing class into the interface a client expects, so classes with incompatible interfaces can work together. Your code expects PaymentGateway.charge(amount), but the vendor SDK offers createTransaction({ value, currency, metadata }); an adapter translates between them.
Adapters are the standard way to integrate third-party libraries, legacy code, and external APIs without spreading their types through your codebase. Combined with DIP, they form the 'adapters' in ports-and-adapters architecture.
Your laptop charger has a US plug; the wall socket is European. The adapter changes the shape without changing the charger or the wall.
When to use it
- Integrating a vendor SDK or legacy class with a different interface.
- Supporting several providers behind one interface.
- Isolating your code from external API changes.
Where it shows up in interviews
Recognize it when: vendor API differs from your interface.
- Design a payment gateway with multiple providers
- Design a notification service with Twilio and SES
Recognize it when: reuse old code in a new design.
- Integrate a legacy inventory system
- Wrap an XML API as JSON
Where it is used in real software
Adapts a byte-based InputStream to a character-based Reader.
Adapts an array to the List interface.
Adapters implement domain ports for databases, message brokers, and HTTP clients.
Key terms
- Target
- The interface the client expects.
- Adaptee
- The existing class with an incompatible interface.
- Adapter
- Implements the target and delegates to the adaptee.
- Object vs class adapter
- Composition (preferred) vs inheritance.
How it works, step by step
- 1Define the target interface
What your domain needs.
- 2Wrap the adaptee
Hold it as a field.
- 3Translate calls
Map parameters, units, and names.
- 4Translate results and errors
Return domain types and exceptions.
- 5Inject the adapter
Clients only see the target.
Payment provider adapters
Target: charge(customerId, amountCents): ChargeResult
| Adaptee | Native call | Adapter translates |
|---|---|---|
| Stripe SDK | paymentIntents.create({amount, currency, customer}) | cents directly, maps status |
| Legacy bank API | debit(accountNo, dollars as string) | cents to '12.34', account lookup |
| PayPal SDK | orders.capture(orderId) | creates then captures, maps errors |
NOWAdaptee: Stripe SDK | Native call: paymentIntents.create({amount, currency, customer}) | Adapter translates: cents directly, maps status
Business code calls one interface; each provider's quirks stay in its adapter.
Implementation
// Target interface owned by our domaininterface PaymentGateway { charge(customerId: string, amountCents: number): Promise<{ ok: boolean; reference: string }>;} // Adaptee: legacy client with a different shapeclass LegacyBankClient { async debit(account: string, amount: string): Promise<{ code: "00" | "51" | "91"; txn: string }> { return { code: "00", txn: "TX-" + account + "-" + amount }; }} class LegacyBankAdapter implements PaymentGateway { constructor(private bank: LegacyBankClient, private accounts: Map<string, string>) {} async charge(customerId: string, amountCents: number) { const account = this.accounts.get(customerId); if (!account) throw new Error("No bank account on file"); const res = await this.bank.debit(account, (amountCents / 100).toFixed(2)); // unit conversion return { ok: res.code === "00", reference: res.txn }; // result mapping }}Complexity and performance
Negligible.
Isolates each vendor.
Trade-offs
A common target interface may hide provider-specific capabilities.
Adds a class, but prevents vendor types from spreading.
Variants and related techniques
Implements both interfaces.
DDD term for a larger adapter translating an entire external model.
Common mistakes
- Leaking adaptee exceptions or types.
Fix: Translate them to domain types.
- Adding business logic to adapters.
Fix: Adapters translate; rules belong in the domain.
Interview questions
Adapter vs Facade?
Adapter makes an existing interface match a different expected interface. Facade provides a new simplified interface over a complex subsystem. Adapter changes shape; facade simplifies.
Adapter vs Decorator?
Adapter changes the interface; Decorator keeps the same interface and adds behavior.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Adapt an XML weather API to a JSON interface | Easy | Translation. |
| Support Stripe and PayPal behind one gateway | Medium | Result and error mapping. |