Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Adapter Pattern

Make incompatible interfaces work together - bridge the gap between old and new!

Now let’s dive into the Adapter Pattern - one of the most commonly used structural design patterns.

Imagine you’re traveling to Europe with a US phone charger. The plug doesn’t fit European sockets! You need an adapter to make your US plug work with European sockets. The Adapter Pattern works the same way!

The Adapter Pattern lets incompatible interfaces work together. It acts as a bridge between two incompatible interfaces, allowing them to collaborate without modifying their source code.

The Adapter Pattern is useful when:

  1. You need to use existing classes - But their interfaces don’t match what you need
  2. Integrating third-party libraries - Library interface doesn’t match your code
  3. Legacy code integration - Old code needs to work with new code
  4. Multiple incompatible interfaces - Need to make them work together
  5. You want to avoid modifying - Don’t want to change existing code

What Happens If We Don’t Use Adapter Pattern?

Section titled “What Happens If We Don’t Use Adapter Pattern?”

Without the Adapter Pattern, you might:

  • Modify existing code - Risk breaking working code
  • Create wrapper code - Scattered throughout your codebase
  • Tight coupling - Code depends on incompatible interfaces
  • Code duplication - Similar conversion logic repeated everywhere
  • Violate Open/Closed Principle - Need to modify code to integrate new systems

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Adapter Pattern works in practice - showing how incompatible interfaces are made compatible:

sequenceDiagram
    participant Client
    participant MediaPlayer as MediaPlayer Interface
    participant Adapter as MediaAdapter
    participant Mp4Player
    participant VlcPlayer
    
    Client->>MediaPlayer: play("mp4", "movie.mp4")
    activate MediaPlayer
    MediaPlayer->>Adapter: play("mp4", "movie.mp4")
    activate Adapter
    Adapter->>Adapter: Check audio_type
    Adapter->>Mp4Player: play_mp4("movie.mp4")
    activate Mp4Player
    Mp4Player-->>Adapter: Playing MP4
    deactivate Mp4Player
    Adapter-->>MediaPlayer: Success
    deactivate Adapter
    MediaPlayer-->>Client: Media playing
    deactivate MediaPlayer
    
    Note over Client,VlcPlayer: Adapter converts interface<br/>to make it compatible!
    
    Client->>MediaPlayer: play("vlc", "video.vlc")
    activate MediaPlayer
    MediaPlayer->>Adapter: play("vlc", "video.vlc")
    activate Adapter
    Adapter->>Adapter: Check audio_type
    Adapter->>VlcPlayer: play_vlc("video.vlc")
    activate VlcPlayer
    VlcPlayer-->>Adapter: Playing VLC
    deactivate VlcPlayer
    Adapter-->>MediaPlayer: Success
    deactivate Adapter
    MediaPlayer-->>Client: Media playing
    deactivate MediaPlayer

You’re building a media player application. You have an existing Mp3Player that works great, but you need to support Mp4Player and VlcPlayer which have different interfaces. Without Adapter Pattern:

Problems:

  • Client needs to know about all different player interfaces
  • Different method names for each player type
  • Hard to add new players - need to modify existing code
  • Violates Open/Closed Principle
classDiagram
    class MediaPlayer {
        <<interface>>
        +play(audio_type, filename) void
    }
    class Mp3Player {
        +play_mp3(filename) void
    }
    class Mp4Player {
        +play_mp4(filename) void
    }
    class VlcPlayer {
        +play_vlc(filename) void
    }
    class MediaAdapter {
        -mp4Player: Mp4Player
        -vlcPlayer: VlcPlayer
        +play(audio_type, filename) void
    }
    class AudioPlayer {
        -adapter: MediaAdapter
        +play(audio_type, filename) void
    }
    
    MediaPlayer <|.. Mp3Player : implements
    MediaPlayer <|.. MediaAdapter : implements
    MediaAdapter --> Mp4Player : adapts
    MediaAdapter --> VlcPlayer : adapts
    AudioPlayer --> MediaPlayer : uses
    
    note for MediaAdapter "Converts incompatible\ninterfaces to compatible ones"
    note for Mp4Player "Incompatible interface"
    note for VlcPlayer "Incompatible interface"

Real-World Software Example: Payment Gateway Integration

Section titled “Real-World Software Example: Payment Gateway Integration”

Now let’s see a realistic software example - integrating multiple payment gateways with different interfaces into a unified payment system.

You’re building an e-commerce system that needs to support multiple payment gateways (Stripe, PayPal, Square). Each gateway has a different interface. Without Adapter Pattern:

Problems:

  • Client needs to know about all different gateway interfaces
  • Different method names and parameters for each gateway
  • Hard to add new gateways - need to modify existing code
  • Payment data format differs for each gateway

There are two main ways to implement the Adapter Pattern:

Uses composition - adapter contains an instance of the adaptee:

Pros: More flexible, can adapt multiple adaptees
Cons: Requires adaptee instance

Uses inheritance - adapter extends both target and adaptee:

Pros: No need for adaptee instance
Cons: Less flexible, language-dependent (Java doesn’t support multiple inheritance)


Use Adapter Pattern when:

Integrating third-party libraries - Library interface doesn’t match your code
Legacy code integration - Old code needs to work with new code
Multiple incompatible interfaces - Need to make them work together
You want to avoid modifying - Don’t want to change existing code
Unified interface needed - Want consistent interface for similar functionality

Don’t use Adapter Pattern when:

You can modify the code - If you can change the incompatible interface, do that instead
Simple interface mismatch - If mismatch is trivial, direct modification might be better
Over-engineering - Don’t add complexity for simple cases
Performance critical - Adapter adds a layer of indirection (usually negligible)


Mistake 1: Modifying Existing Code Instead of Adapting

Section titled “Mistake 1: Modifying Existing Code Instead of Adapting”

Mistake 2: Creating Adapters for Simple Cases

Section titled “Mistake 2: Creating Adapters for Simple Cases”

Mistake 3: Not Handling Data Transformation

Section titled “Mistake 3: Not Handling Data Transformation”

  1. Reusability - Use existing classes without modification
  2. Flexibility - Easy to add new adapters for new incompatible classes
  3. Separation of Concerns - Adapter handles compatibility, client stays clean
  4. Open/Closed Principle - Open for extension (new adapters), closed for modification
  5. Legacy Integration - Integrate old code with new code easily

Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.

  • Integrate incompatible interfaces - Make old and new code work together
  • Use third-party libraries - Library interface doesn’t match your code
  • Avoid modifying code - Don’t want to change existing working code
  • Unified interface - Want consistent interface for similar functionality
  • Legacy integration - Integrate legacy systems with new systems
  1. Define target interface - What the client expects
  2. Identify adaptee - The incompatible class
  3. Create adapter - Implements target interface, wraps adaptee
  4. Transform calls - Adapter converts target calls to adaptee calls
  5. Client uses adapter - Client uses unified interface
Client → Target Interface → Adapter → Adaptee
  • Target Interface - What the client expects
  • Adaptee - The incompatible class
  • Adapter - Bridges target and adaptee
  • Client - Uses the target interface
class Target:
def request(self): pass
class Adaptee:
def specific_request(self): pass
class Adapter(Target):
def __init__(self):
self.adaptee = Adaptee()
def request(self):
return self.adaptee.specific_request() # Adapt!

✅ Integrating third-party libraries
✅ Legacy code integration
✅ Multiple incompatible interfaces
✅ Want to avoid modifying existing code
✅ Need unified interface

❌ You can modify the code directly
❌ Simple interface mismatch
❌ Over-engineering simple cases
❌ Performance critical (adds indirection)

  • Adapter Pattern = Makes incompatible interfaces compatible
  • Target = Interface client expects
  • Adaptee = Incompatible class
  • Adapter = Bridge between target and adaptee
  • Benefit = Reuse code without modification
  • Use Case = Third-party libraries, legacy integration
class Target:
def request(self): pass
class Adaptee:
def specific_request(self): pass
class Adapter(Target):
def __init__(self):
self.adaptee = Adaptee()
def request(self):
return self.adaptee.specific_request()
# Usage
adapter = Adapter()
adapter.request() # Works!
  • Adapter Pattern bridges incompatible interfaces
  • It allows reuse without modification
  • Use Object Adapter (composition) in most cases
  • Don’t use it for simple cases - avoid over-engineering
  • It’s about compatibility, not just wrapping!

What to say:

“Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, allowing them to collaborate without modifying their source code.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must mention:

  • Integrating third-party libraries - Library interface doesn’t match your code
  • Legacy code integration - Old code needs to work with new code
  • Multiple incompatible interfaces - Need to make them work together
  • Avoid modifying code - Don’t want to change existing code

Example scenario to give:

“I’d use Adapter Pattern when integrating a third-party payment gateway like Stripe into my e-commerce system. Stripe has its own interface (charge_stripe()), but my system expects a unified payment interface (process_payment()). I create a StripeAdapter that implements my payment interface and internally calls Stripe’s methods.”

Must discuss:

  • Object Adapter: Uses composition, more flexible
  • Class Adapter: Uses inheritance, language-dependent
  • Preference: Object Adapter is preferred in most cases

Example to give:

“I prefer Object Adapter because it uses composition, making it more flexible. I can adapt multiple adaptees or change behavior at runtime. Class Adapter uses inheritance, which is less flexible and doesn’t work well in languages like Java that don’t support multiple inheritance.”

Benefits to mention:

  • Reusability - Use existing classes without modification
  • Flexibility - Easy to add new adapters
  • Separation of Concerns - Adapter handles compatibility
  • Open/Closed Principle - Open for extension, closed for modification

Trade-offs to acknowledge:

  • Complexity - Adds a layer of indirection
  • Performance - Small overhead (usually negligible)
  • Over-engineering risk - Can be overkill for simple cases

Q: “What’s the difference between Adapter Pattern and Decorator Pattern?”

A:

“Adapter Pattern changes the interface of an object to make it compatible with another interface. Decorator Pattern adds new behavior to an object without changing its interface. Adapter is about compatibility, Decorator is about adding functionality.”

Q: “When would you use Adapter vs modifying the code directly?”

A:

“I use Adapter when I can’t or shouldn’t modify the code - like third-party libraries, legacy systems, or when modifying would break existing functionality. If I own the code and modification is safe, I might modify directly. But Adapter is safer and follows Open/Closed Principle.”

Q: “How does Adapter Pattern relate to SOLID principles?”

A:

“Adapter Pattern primarily supports the Open/Closed Principle - you can extend functionality (add new adapters) without modifying existing code. It also supports Single Responsibility Principle by separating the adaptation logic into its own class. Additionally, it helps with Dependency Inversion Principle by allowing clients to depend on abstractions (target interface) rather than concrete implementations.”

Before your interview, make sure you can:

  • Define Adapter Pattern clearly in one sentence
  • Explain when to use it (with examples)
  • Describe Object Adapter vs Class Adapter
  • Implement Adapter Pattern from scratch
  • Compare with other structural patterns (Decorator, Facade)
  • List benefits and trade-offs
  • Identify common mistakes
  • Give 2-3 real-world examples
  • Connect to SOLID principles
  • Discuss when NOT to use it

Remember: Adapter Pattern is about making incompatible interfaces work together - bridging the gap between old and new, third-party and your code! 🔌