The Adapter Design Pattern
If you have ever traveled internationally, you know the frustration of staring at a wall outlet that looks completely different from the plug on your laptop charger. You don’t throw away your laptop, and you certainly don’t rewire the hotel’s electrical grid. Instead, you use a travel adapter—a small device that sits between your plug and the wall, translating the interface of one to match the other.
In software engineering, the Adapter Pattern (sometimes called the Wrapper pattern) does exactly the same thing. It is a structural design pattern that allows objects with incompatible interfaces to collaborate. It acts as a “universal translator” between your application’s clean internal code and messy external dependencies, legacy systems, or third-party libraries.
Join a community of senior engineers. Subscribe and get FREE access to System Design deep dives.
The Problem: The Square Peg and the Round Hole
Imagine you are building a modern alert system for your company. Your core application uses a standard Notification interface that expects every service to implement a send() method.
You start with email, which works perfectly. But then, the business asks you to integrate a third-party Slack SDK. You download the SDK, but you discover that the Slack library doesn’t have a send() method. Instead, it has a post_to_channel() method, and it expects a completely different data format.
Without the Adapter pattern, you might be tempted to scatter if/else checks throughout your core application:
def trigger_alert(service, title, message):
# BAD: The core app is now coupled to the Slack SDK's specific API!
if isinstance(service, SlackAPI):
formatted_text = f"*{title}*\n{message}"
service.post_to_channel("C12345", formatted_text)
else:
service.send(title, message)Why is this bad?
Your core application is now deeply entangled with the specifics of the Slack SDK. If Slack updates their API tomorrow and renames the method to publish_message(), you have to hunt down and change your core business logic.
Build Redis, Git, Docker & Claude Code From Scratch
Want to become a better software engineer?
Trusted by engineers at Google, NVIDIA, and Anthropic, CodeCrafters helps you rebuild technologies like Redis, Git, Docker, and Claude Code from scratch so you can deeply understand how real systems work.
We’ve partnered with CodeCrafters to give all readers an exclusive 40% lifetime discount.
💡 Engineers may expense platforms like this through their team’s learning budget.
The Solution: The Wrapper
The Adapter Pattern solves this by introducing a middleman class (the Adapter). This class implements your application’s expected interface but quietly translates the calls under the hood into a format the incompatible service (the Adaptee) understands.
The Anatomy of the Pattern
The Target Interface: The domain-specific interface that your core application expects to work with.
The Adaptee: The existing, incompatible class or third-party library that contains the useful behavior.
The Adapter: The class that bridges the gap. It implements the Target Interface and holds a reference to the Adaptee.
Refactoring to the Adapter Pattern
Let’s clean up our notification system using Python:
from abc import ABC, abstractmethod
# 1. The Target Interface
# This is the "round hole" our application expects.
class NotificationProvider(ABC):
@abstractmethod
def send(self, title: str, message: str) -> None:
pass
# A compatible class that already fits the Target Interface
class EmailService(NotificationProvider):
def send(self, title: str, message: str) -> None:
print(f"Sending Email -> Subject: {title} | Body: {message}")
# 2. The Adaptee
# This is the third-party Slack SDK. We CANNOT change this code. It's the "square peg".
class ExternalSlackAPI:
def post_to_channel(self, channel_id: str, text: str) -> None:
print(f"Slack API posting to {channel_id}: {text}")
# 3. The Adapter
# This class implements our expected interface, but wraps the Adaptee.
class SlackAdapter(NotificationProvider):
def __init__(self, slack_api: ExternalSlackAPI, channel_id: str):
self.slack_api = slack_api
self.channel_id = channel_id
def send(self, title: str, message: str) -> None:
# Translate the data format
formatted_markdown = f"*{title}*\n> {message}"
# Translate the method call
self.slack_api.post_to_channel(self.channel_id, formatted_markdown)Join a community of senior engineers. Subscribe and get FREE access to System Design deep dives.
See It in Action
Now, your core application can treat everything like a standard NotificationProvider. It doesn’t know (and doesn’t care) that Slack uses a completely different API under the hood.
# The core system just wants a list of compatible providers
def broadcast_alert(providers: list[NotificationProvider], title: str, message: str):
for provider in providers:
# The core app blindly calls .send() on everything
provider.send(title, message)
# Setup
email = EmailService()
slack_sdk = ExternalSlackAPI()
# We wrap the incompatible SDK in our Adapter
slack_adapter = SlackAdapter(slack_sdk, "C999_ALERTS")
# Execute
broadcast_alert([email, slack_adapter], "System Outage", "Database is down!")
# Output:
# Sending Email -> Subject: System Outage | Body: Database is down!
# Slack API posting to C999_ALERTS: *System Outage*
# > Database is down!Enterprise Use Cases
The Adapter pattern is a lifesaver in large codebases, particularly when dealing with system migrations and external boundaries:
Third-Party APIs and SDKs: Instead of letting Stripe, Twilio, or AWS SDK objects bleed into your core business logic, you create a
PaymentProcessorAdapterorSMSAdapter. If you ever switch vendors, you only have to write a new adapter; your core app remains untouched.Legacy Code Migration: When incrementally strangling a legacy system, you can write an adapter that makes the old legacy database look like the shiny new ORM interface your modern code expects.
Data Format Translation: Adapting an older service that returns XML into a format that satisfies a modern component expecting JSON.
Key Takeaway: Use the Adapter Pattern as a defensive barrier around your core application. By forcing external libraries and legacy code to adapt to your interfaces (rather than the other way around), you protect your application from vendor lock-in and sweeping, breaking changes.
Join a community of senior engineers. Subscribe and get FREE access to System Design deep dives.
Invite your friends and earn rewards
© 2026 The Coding Gopher
548 Market Street PMB 72296, San Francisco, CA 94104