StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsAdapter

Adapter

A structural pattern where a thin translator object lets an existing class serve an interface it was never written for, with both originals left untouched.

Definition

The sensor library speaks Fahrenheit, your app speaks Celsius, and neither side will change for you: one belongs to a vendor and the other has 40 call sites. Adapter is the Gang of Four structural pattern that lets the interface of an existing class be used as another interface, through a thin object that translates between them, and its other recorded name is simply wrapper. Neither original class gets edited. The adapter stands between them and speaks both languages.


An adapter is really just a translator object: it implements the interface your code expects and forwards every call to the thing that doesn't, converting on the way through. That makes it the rare pattern you've certainly used already, because every time a library hands you the wrong shape and you wrap it, you're here.

The Conversion That Multiplies

Skip the pattern and the translation doesn't disappear, it multiplies. Below, a third-party FahrenheitSensor feeds two functions, each carrying its own pasted copy of the Fahrenheit-to-Celsius formula. The first copy is right. The second lost its parentheses somewhere between editor and commit, and operator precedence did the rest. Trace what log_temperature computes for boiling water before reading the comments:

The broken line evaluates 32 * 5 / 9 first, about 17.8, and subtracts that from 212, logging boiling water as 194.2 degrees Celsius. The Java, C#, C++, and Go tabs are sneakier still: integer division turns the same expression into exactly 17, so they log 195.0. One formula, two languages' worth of distinct wrong answers, and every additional call site is another chance to mint a new one.

One Translator, One Place

The adapter version declares the interface the app actually wants, TemperatureSource with a single celsius() method, and builds exactly one bridge to the sensor. CelsiusAdapter implements the Target, holds the FahrenheitSensor, and owns the formula. Before reading past the code, confirm the prediction the last section set up: what does display_temperature print now, and how many copies of the conversion exist?

It prints 100.0 C, and the formula appears exactly once, inside the adapter's celsius(). The display function takes any TemperatureSource and never learns Fahrenheit exists, the sensor class is untouched vendor code, and the parenthesis bug from the last section now has exactly one line in the codebase where it could live, which is also the line a reviewer will read most carefully.

Who Does What

Three roles, with names that describe the geometry. The Target is the interface the client demands (TemperatureSource). The Adaptee is the existing class with the wrong interface and the right behavior (FahrenheitSensor). The Adapter implements the Target while holding the Adaptee, turning each incoming call into one or more outgoing ones. The client sees only the Target, the adaptee only its own interface, and neither knows the other exists.


The pattern is named after the travel plug adapter, and the mapping is nearly exact: your laptop's plug is the Target shape, the foreign wall socket is the Adaptee, and the adapter presents one face to each. The analogy breaks at the most instructive spot. A travel adapter changes only the shape and passes the electricity through untouched, while software adapters routinely convert the payload too, the way Fahrenheit became Celsius. That conversion logic is the part that can be wrong, which is exactly why the pattern insists it exist in one place.

Adapter: Step-by-Step

Installing the pattern is four moves:

1. Name the Interface Your Code Wants

Write the Target from the client's point of view: the app thinks in Celsius, so the interface is celsius(). Deriving it from the adaptee's shape instead produces a wrapper that translates nothing.

2. Hold the Incompatible Thing, Don't Touch It

The adapter stores the FahrenheitSensor as a private field, leaving the third-party class byte-for-byte unmodified. Composition is the whole trick: the adaptee never learns it's being translated.

3. Translate in Exactly One Place

Every Target method maps to one or more adaptee calls plus whatever conversion the mismatch demands, like (f - 32) * 5 / 9. That formula now exists once, reviewed once, fixed once if it's ever wrong.

4. Plug It In Wherever the Target Goes

display_temperature() accepts any TemperatureSource, so the adapter slots in beside native implementations indistinguishably. The client gains a sensor without gaining a dependency on Fahrenheit.
Without Inheritance: Go and Rust

Go barely notices this pattern is a pattern. A struct holds the adaptee, implements the target interface, and structural typing handles the rest, no declaration connecting the two. The Go tab above is the entire idiom, and it reads like ordinary Go because it is ordinary Go.


Rust gives the adapter a name of its own: the newtype pattern. The language's orphan rule forbids implementing a foreign trait on a foreign type, so when a library type needs to speak a trait it doesn't know, you wrap it in a one-field tuple struct like CelsiusAdapter(FahrenheitSensor), and the wrapper, being local, can implement anything. The result is the GoF adapter arrived at from a completely different direction, less a style choice than the compiler's required route for after-the-fact interface translation.

Which Wrapper Is This?

You might think adapter and wrapper are interchangeable words, and Wikipedia even lists wrapper as this pattern's alternate name while noting the same alias belongs to Decorator. Wrapper describes the mechanism, holding an object and forwarding to it, which several patterns share. Adapter is the wrapper whose entire point is changing the interface: in goes celsius(), out comes read_fahrenheit(). A wrapper that keeps the same interface and adds behavior is a decorator, and one that keeps the same interface to control access is a proxy. Same shape three times, sorted by intent.


The other classic mix-up is Adapter versus Bridge, and timing settles it. An adapter is retrofitted between two interfaces that already exist and already disagree, which is why it appears when a codebase meets a library. A bridge is drawn up front, splitting one design into two hierarchies that will grow independently. Adapter repairs a mismatch after the fact, and Bridge prevents one from forming, so by the time you're reaching for a wrapper, the bridge moment has usually passed.

When It's a Smell

The failure mode is the leaky adapter. When the Target promises operations the Adaptee can't honestly perform, the adapter starts faking: returning empty defaults, throwing from half its methods, or silently dropping data the conversion can't carry. Callers get an interface that type-checks and lies. A second smell is stacking, an adapter wrapping an adapter because two teams each wrapped their own boundary, where each layer adds a hop and hides the original behavior a little deeper.


Both smells share a root: the adapter got used as a substitute for agreeing on an interface. The pattern shines when the adaptee is genuinely immovable, vendor code, a standard library, a legacy module nobody may touch. When both sides are yours, changing one of them beats translating between them forever.

Best Practices

The mechanics take five minutes, and these five details decide whether the translation holds:


The interface should read like the client's wish list, not like the adaptee with the names changed. An adapter whose methods mirror the adaptee one-to-one has translated vocabulary instead of meaning.

Check Yourself

Two questions before you go. First: the inline version logged boiling water at 194.2 degrees. What property of the adapter version makes that class of bug structurally rarer, not just less likely? Second: Adapter and Facade both stand between your code and someone else's. What single question tells them apart at a glance? Both answers are on this page, the second in Which Wrapper Is This.


Then go spot it in the wild, starting with a line you've probably typed: new BufferedReader(new InputStreamReader(System.in)). Java's InputStreamReader translates byte streams into character streams, a textbook adapter between two interfaces that couldn't talk, and Oracle's own docs charmingly call it a "bridge", proof that even the JDK's authors mix up these names. Last, grep your own codebase for functions named convert or toSomething that are called from more than one place. Each one is a translation living in the open, waiting for the interface that would give it a single home.

the_inline_conversion.py

class FahrenheitSensor:
    # Third-party hardware library: not yours to edit
    def read_fahrenheit(self) -> float:
        return 212.0

def display_temperature(sensor: FahrenheitSensor) -> str:
    # Conversion pasted inline, correctly this time
    celsius = (sensor.read_fahrenheit() - 32) * 5 / 9
    return f"{celsius:.1f} C"

def log_temperature(sensor: FahrenheitSensor) -> str:
    # Same conversion, pasted again, parentheses lost in transit
    celsius = sensor.read_fahrenheit() - 32 * 5 / 9
    return f"logged {celsius:.1f} C"

# Example usage
sensor = FahrenheitSensor()
print(display_temperature(sensor))  # 100.0 C
print(log_temperature(sensor))      # logged 194.2 C  <- boiling water, apparently

adapter.py

from abc import ABC, abstractmethod

class TemperatureSource(ABC):  # Target: the interface the app speaks
    @abstractmethod
    def celsius(self) -> float: ...

class FahrenheitSensor:  # Adaptee: third-party, not yours to edit
    def read_fahrenheit(self) -> float:
        return 212.0

class CelsiusAdapter(TemperatureSource):  # Adapter: speaks Target, holds Adaptee
    def __init__(self, sensor: FahrenheitSensor):
        self._sensor = sensor

    def celsius(self) -> float:
        # The one and only conversion site in the codebase
        return (self._sensor.read_fahrenheit() - 32) * 5 / 9

def display_temperature(source: TemperatureSource) -> str:
    return f"{source.celsius():.1f} C"

# Example usage
adapter = CelsiusAdapter(FahrenheitSensor())
print(display_temperature(adapter))  # 100.0 C