codejump

AcademyDesign Patterns

Adapter pattern

Translating an interface you cannot change into the one callers expect, the extra hop every call pays, and the moment it adds behavior and becomes a Decorator.

5 min readUpdated

3 exercises

Waiting at the end of this page.

Adapter is the translation layer of the seven structural patterns: the one for when the interface you have and the interface the caller expects are simply different.

An Adapter sits between code that expects one interface and an object that offers a different, incompatible one, usually because the object is a legacy class or a third-party library you can't rewrite. The adapter's whole job is translation: it implements the interface the caller expects, and internally calls whatever the wrapped object actually exposes.

The problem it solves

Your checkout code calls gateway.charge(amount). It works with the payment provider you started with. Then you integrate a second provider, and its SDK looks nothing like the first: different method names, different argument shapes, different error types.

You have three bad options:

  • Edit the SDK. It is third-party code, and your change disappears on the next update.
  • Branch in the caller. if (provider === 'paypal') ... else ... spreads provider details through your business logic.
  • Rewrite the caller to match each SDK. Every new provider forces changes everywhere the caller is used.

The fix is to leave both sides alone and put a small object between them that speaks your interface on one end and the SDK's on the other.

Structure

The client calls print(text). The adapter translates that into the old class's printOld(text), and the old class never knows an adapter exists
The client calls print(text). The adapter translates that into the old class's printOld(text), and the old class never knows an adapter exists

Three parts:

  • The target interface: what the caller expects (print(text)).
  • The adaptee: the existing class with an incompatible interface (OldPrinter).
  • The adapter: implements the target interface and forwards each call to the adaptee, translating names and arguments on the way.
class OldPrinter {
  printOld(text) {
    return 'OLD:' + text;
  }
}

class PrinterAdapter {
  constructor(oldPrinter) {
    this.oldPrinter = oldPrinter;
  }
  print(text) {
    return this.oldPrinter.printOld(text);
  }
}

Nothing about OldPrinter changed. It doesn't even know an adapter exists. That is the point: Adapter is what you reach for when you're not allowed to touch the class you're wrapping.

Where it is used in real systems

The app owns one PaymentGateway interface. Each provider gets its own adapter that hides the SDK's shape
The app owns one PaymentGateway interface. Each provider gets its own adapter that hides the SDK's shape
Use caseWhat is being adapted
Third-party SDKsStripe, Lemon Squeezy or PayPal behind one PaymentGateway interface owned by your app.
Legacy codeAn old module with callbacks wrapped to expose the promise-based API the rest of the app uses.
Data formatsAn external API's snake_case JSON mapped to your app's camelCase domain objects.
Storage and email vendorsS3, Cloudflare R2 or local disk behind one Storage interface.
Test isolationA fake adapter standing in for the real one, so tests never hit the network.
Database driversOne query interface over several different drivers.
Unit conversionAn adapter turning meters into feet, or cents into dollars, at a system boundary.

The common thread: the boundary between your code and code you don't control. An adapter keeps the foreign shape from spreading past that boundary.

Adapters you already use

  • `util.promisify(fn)` wraps a Node callback-style function so it exposes a promise interface. It is an adapter between two calling conventions.
  • Prisma and ORM layers adapt several SQL dialects to one query API.
  • `fetch` wrappers that reshape a raw Response into your typed result and error format.
  • Clerk and Lemon Squeezy webhook handlers that convert a vendor's event payload into your own event type before it enters the domain.
  • Angular `HttpInterceptor` and React Query `select` functions, when they map a backend response into the shape the UI expects.

Adapter, Decorator, Proxy and Facade

Four patterns wrap an object, and interviewers love asking how they differ.

PatternInterface compared with the wrapped objectPurpose
AdapterDifferentTranslate an incompatible interface
DecoratorSameAdd behavior, stackable
ProxySameControl access or defer cost
FacadeNew, simplerHide a complex subsystem

If the interface changes, it is an Adapter. If it stays the same and behavior is added, it is a Decorator.

When not to use it

  • You control both sides. Just change one of them. An adapter that translates between two classes you own is a sign that one of the interfaces is wrong.
  • One trivial call. Wrapping a single function in a class adds ceremony for nothing. A small mapping function is enough.
  • Many hops. An adapter costs one extra call on every request. That is nothing in most code, but it matters in a hot loop.
  • Leaky translation. If the caller has to know which adapter it got, the adapter is not doing its job.

What interviewers ask

  • "What is the Adapter pattern?" Translating one interface into another that a caller expects, without changing either side.
  • "Adapter versus Decorator?" Adapter changes the interface, Decorator keeps it and adds behavior.
  • "Adapter versus Facade?" Adapter makes one existing interface match another. Facade defines a new, simpler interface over many objects.
  • "Give a real example." A payment gateway interface with one adapter per provider, or util.promisify.
  • "Where does it help testing?" You depend on your own interface, so tests inject a fake adapter instead of mocking a vendor SDK.

The mistake to avoid

An Adapter that also tries to add new behavior instead of pure translation. That is not an Adapter anymore, it's a Decorator wearing an Adapter's name, and it muddies why either pattern was reached for. Keep the adapter thin: rename, reshape, forward. Put logging, caching and retries in a separate layer.

The mechanism above, an object holding a reference to another and forwarding calls, is the same in JavaScript, Python or Java. Only the syntax changes.

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.