Suggest an editImprove this articleRefine the answer for “What is Adapter design pattern?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Adapter design pattern** converts one interface into another that a client expects, so incompatible classes work together without changing their code. ```javascript class OldGateway { processPayment(amt) { /*...*/ } } class Adapter { constructor(gw) { this.gw = gw; } pay(amt) { this.gw.processPayment(amt); } } ``` **Key point:** composition with translation, not rewriting.Shown above the full answer for quick recall.Answer (EN)Image**Adapter design pattern** converts the interface of one class into another interface that clients expect, so incompatible classes can work together without changing their code. ## Theory ### TL;DR - Power adapter analogy: your US plug doesn't change, the adapter bridges the shape mismatch between plug and socket. - The pattern wraps an existing class and delegates calls with translation, not rewriting. - Main trigger: interface mismatch blocks integration and you can't modify either side. - Decision rule: can't change the old class AND can't change the new system? Adapter is the answer. - Adapter sits at the seam between two worlds that were never designed to meet. ### Quick example ```javascript // Old printer only knows printRaw(data) class OldPrinter { printRaw(data) { console.log(`Raw: ${data}`); } } // Adapter wraps old printer, exposes printFormatted(text) class PrinterAdapter { constructor(oldPrinter) { this.old = oldPrinter; } printFormatted(text) { const formatted = text.toUpperCase(); // translate this.old.printRaw(formatted); // delegate } } const adapter = new PrinterAdapter(new OldPrinter()); adapter.printFormatted('hello'); // Output: Raw: HELLO ``` Two things happen here: translation (uppercasing) and delegation (calling the old method). Neither class changes. ### Key difference from rewriting You could rename `printRaw` to `printFormatted`. But what if it's a third-party library? Or 50 other places still call `printRaw`? The adapter sits between the two worlds. Old code keeps working. New code gets the interface it expects. Both sides stay untouched. ### When to use - Legacy API doesn't match what your new code expects, and modifying either side isn't an option. - Third-party library with its own interface: wrap it without forking it. - Multiple services with different signatures: one adapter per service, unified interface for your code. - Test mocks that match the same interface as production code: swap at runtime, no logic changes. ### How it works internally Client calls the adapter's method. The adapter maps parameters to what the adaptee expects, calls the adaptee, then translates the return value back. In JavaScript, structural typing handles this naturally: if the adapter has the right shape, client code accepts it. V8 compiles the wrapper as a thin dispatch layer with no meaningful overhead for typical use cases. If you're working in TypeScript, add `implements IPayment` explicitly. Otherwise the compiler won't catch interface mismatches at build time. Structural typing is convenient, but explicit contracts are safer. ### Common mistakes **1. Partial interface implementation** ```javascript // Wrong: only pay(), missing refund() class PartialAdapter { pay() { /* ... */ } } adapter.refund(); // TypeError: adapter.refund is not a function ``` Implement the full target interface. If a method has no mapping, throw an explicit `NotImplementedError` rather than leaving it undefined. **2. Inheritance instead of composition** ```javascript // Wrong: extends ties adapter to OldGateway's hierarchy class BadAdapter extends OldGateway { pay() { super.processPayment(); } } ``` Use composition. `new Adapter(new OldGateway())` is correct. Inheritance creates tight coupling: if `OldGateway` changes its hierarchy, your adapter breaks. **3. Missing error translation** ```javascript // Wrong: OldError leaks through to caller pay(amount) { this.old.process(amount); } // Right pay(amount) { try { this.old.process(amount); } catch (e) { throw new PaymentError(e.message); // translate to expected error type } } ``` Old code throws `OldAuthError`. New code expects `UnauthorizedError`. The adapter handles that mapping. Otherwise error handling in the client breaks. **4. Assuming structural typing covers everything in TypeScript** JavaScript is flexible about object shapes, but TypeScript with strict mode wants you to explicitly declare `implements IPayment`. It's a common oversight. You get no type errors until runtime in complex codebases if you skip it. ### Real-world usage - `express-jwt` adapts the `jsonwebtoken` library to Express middleware signature. - `react-query` wraps Axios and Fetch behind a unified query function. - AWS SDK v2-to-v3 migration ships with adapter utilities to ease the transition. - Redux-saga adapts between thunk-style and saga-style async effects. ### Follow-up questions **Q:** What is the difference between Adapter and Decorator? **A:** Decorator adds behavior while keeping the same interface. Adapter changes the interface to match what a client expects. Similar structure, different purpose. **Q:** What is the difference between Adapter and Bridge? **A:** Adapter retrofits existing classes that were never designed to work together. Bridge decouples abstraction from implementation at design time, before the problem exists. **Q:** Can you build a two-way adapter? **A:** Yes. Each direction has its own translation. Database ORMs do this: SQL toward the database, objects toward your application code. **Q:** When does Adapter add noticeable overhead? **A:** In deep call chains or when translation is expensive, like converting large data payloads. Profile delegation depth if latency is a concern. **Q:** (Senior) How does Adapter fit with an API Gateway in microservices? **A:** The gateway acts as a system-level adapter. It translates external protocols like REST or gRPC into whatever format internal services expect, and handles versioning without touching service code. ## Examples ### Basic: wrapping a legacy printer ```javascript class OldPrinter { printRaw(data) { console.log(`Raw: ${data}`); } } class PrinterAdapter { constructor(printer) { this.printer = printer; } printFormatted(text) { this.printer.printRaw(text.toUpperCase()); } } const adapter = new PrinterAdapter(new OldPrinter()); adapter.printFormatted('hello'); // Raw: HELLO ``` Old class stays untouched. New callers use `printFormatted`. The adapter handles the translation in between. ### Intermediate: adapting legacy auth to Express middleware Real scenario: your team inherits a callback-based auth library. New Express routes expect `req.user` set by middleware. ```javascript class OldAuthLib { authenticate(userId, callback) { // simulates async DB lookup callback(null, { id: userId, name: 'User' }); } } class AuthMiddlewareAdapter { constructor(auth) { this.auth = auth; } middleware(req, res, next) { this.auth.authenticate(req.body.userId, (err, user) => { if (err) return next(err); req.user = user; // maps to Express convention next(); }); } } const authAdapter = new AuthMiddlewareAdapter(new OldAuthLib()); app.post('/login', (req, res, next) => authAdapter.middleware(req, res, next), (req, res) => res.json(req.user) // { id: 123, name: 'User' } ); ``` The route handler doesn't know it's talking to a legacy system. I've seen this exact pattern save a migration when a third-party auth SDK changed its API overnight: only the adapter needed updating. ### Advanced: payment gateway with full error translation This shows what a production-ready adapter looks like, including error translation and a complete interface. ```javascript class OldPaymentGateway { processPayment(amount) { console.log(`Processing $${amount}`); } reverseTransaction(id) { console.log(`Reversing ${id}`); } } class PaymentError extends Error {} class PaymentAdapter { constructor(legacyGateway) { this.gateway = legacyGateway; } pay(amount, currency) { try { const converted = currency === 'EUR' ? amount * 1.1 : amount; this.gateway.processPayment(converted); } catch (e) { throw new PaymentError(`Payment failed: ${e.message}`); } } refund(transactionId) { try { this.gateway.reverseTransaction(transactionId); } catch (e) { throw new PaymentError(`Refund failed: ${e.message}`); } } } const adapter = new PaymentAdapter(new OldPaymentGateway()); adapter.pay(100, 'EUR'); // Processing $110 adapter.refund('tx-001'); // Reversing tx-001 ``` Both methods are implemented (no partial interface). Errors are translated to a known type. The adapter covers the full contract of the target interface.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.