← Back

Adapter Design Pattern in Python: Making Incompatible Interfaces Work Together

Posted on Thu 28 May 2026 in Python

Modern applications rarely exist in isolation.

A Python application may need to communicate with:

  • Legacy systems
  • Third-party APIs
  • External libraries
  • Different data formats
  • Multiple service providers

A common problem occurs when two components provide similar functionality but expose different interfaces.

For example, your application expects:

payment.pay()

but an old payment library provides:

payment.make_payment()

The functionality is similar, but the interfaces are incompatible.

The Adapter Design Pattern solves this problem by converting one interface into another interface that the client expects.

Refactoring.Guru classifies Adapter as a structural design pattern that allows objects with incompatible interfaces to collaborate.

What Is the Adapter Pattern?

Think of an adapter as a translator.

Client
  |
  | expected interface
  v
Adapter
  |
  | translated request
  v
Existing Service

The existing service does not need to be modified. The Adapter sits between the application and the service.

Python Example: Payment System

Suppose our application expects every payment service to provide:

pay()

We define:

class PaymentService:

    def pay(self, amount):
        pass

Now imagine we have an old third-party service:

class OldPaymentService:

    def make_payment(self, amount):
        print(f"Processing payment of ₹{amount}")

The problem is:

Application expects: pay()

Old service provides: make_payment()

We cannot directly use the old service through the expected interface.

Creating the Adapter

class PaymentAdapter:

    def __init__(self, old_payment_service):
        self.old_payment_service = old_payment_service

    def pay(self, amount):
        self.old_payment_service.make_payment(amount)

Now:

old_service = OldPaymentService()

payment = PaymentAdapter(old_service)

payment.pay(1000)

Output:

Processing payment of ₹1000

The application calls pay(). The Adapter internally converts that request into make_payment().

The Architecture

The complete flow is:

Application
     |
     | pay(1000)
     v
PaymentAdapter
     |
     | make_payment(1000)
     v
OldPaymentService

The application does not need to know how the old service works.

Why Not Modify the Existing Class?

You might ask: "Why don't we simply rename make_payment() to pay()?"

In real projects, that may not be possible. The existing service could be:

  • A third-party library
  • A legacy application
  • Used by other systems
  • Maintained by another team
  • Closed-source

Changing it could break existing applications.

Refactoring.Guru describes this exact type of situation: an existing service may have an incompatible interface, and modifying it may be impossible or risky.

The Adapter allows us to integrate it without changing the original service.

Another Example: Data Format Conversion

Imagine your application receives stock data in XML, but a third-party analytics library requires JSON.

Direct integration is impossible.

An adapter can perform the conversion:

Your Application
      |
      | XML
      v
XML → JSON Adapter
      |
      | JSON
      v
Analytics Library

This is similar to the stock-market example used by Refactoring.Guru to demonstrate converting data between incompatible formats.

Real-World Analogy

A power plug adapter is a simple real-world example.

Suppose your device has one type of plug:

Device Plug
     ↓
   Adapter
     ↓
Wall Socket

The adapter allows the device to work with a socket that uses a different physical standard.

Software adapters work similarly. They translate one interface into another.

Adapter Structure

The typical object Adapter pattern contains:

Client — The application that wants to use a service.

Target Interface — The interface expected by the client. Example: pay()

Adaptee — The existing service with the incompatible interface. Example: make_payment()

Adapter — The object that connects the two.

class PaymentAdapter:

    def pay(self, amount):
        self.service.make_payment(amount)

Refactoring.Guru describes this structure as the adapter implementing the client's expected interface while wrapping the incompatible service.

Adapter vs Facade

These patterns can sometimes be confused.

Adapter makes one interface compatible with another.

Interface A
    ↓
Adapter
    ↓
Interface B

Facade provides a simplified interface to a complex subsystem.

Client
  ↓
Facade
  ↓
Complex Subsystem

A simple way to remember: Adapter changes the interface; Facade simplifies the interface.

Adapter vs Wrapper

Adapter is also commonly called a Wrapper.

However, not every wrapper is necessarily an implementation of the Adapter pattern. The important characteristic is that the wrapper provides an interface compatible with what the client expects.

Advantages

Reuse Existing Code — You can integrate classes that were not originally designed for your application.

Protects Legacy Code — Existing systems do not need to be modified.

Reduces Coupling — The client depends on the Adapter rather than directly depending on the incompatible service.

Easier Integration — Different APIs and libraries can be integrated behind a consistent interface.

When Should You Use Adapter?

Use the Adapter pattern when:

  • An existing class has the wrong interface
  • You need to integrate a third-party library
  • You need to support legacy code
  • Multiple services expose different interfaces
  • Data or method formats need conversion
  • You want to isolate external dependencies from your core application

Conclusion

The Adapter pattern solves a very practical software problem: "I have an existing component that does what I need, but its interface doesn't match what my application expects."

Instead of modifying the existing component, introduce an Adapter.

Client
  ↓
Adapter
  ↓
Existing Service

In Python applications, Adapter is particularly useful when integrating payment providers, external APIs, legacy systems, database libraries, cloud services, and third-party packages.

The simplest way to remember it is: Adapter = Translator between incompatible interfaces.