skip to content

What problem does the Adapter design pattern (Gang of Four, structural category) solve, and what are its participants?

level: juniorimportance: must knowfreq 72%

answer

  1. Convert interface → what client expects
  2. Target / Client / Adaptee / Adapter
  3. Retrofit glue, not new behavior
  4. Translate shape, data, units, errors
  5. Named for what it produces

basics

~20 s

Adapter converts one type's interface into the interface a caller already expects, so two otherwise incompatible pieces of code can work together. The adapter wraps the existing type and translates each call, without changing either side.

solid answer

~50 s

Adapter is a structural pattern whose intent is: convert the interface of a class into another interface clients expect, so classes that could not otherwise collaborate can. Four participants: the Target (the interface the client is coded against), the Client (calls only the Target), the Adaptee (existing, useful code with the wrong interface — a legacy class, a third-party SDK, a vendor library), and the Adapter (implements Target and translates each Target call into one or more Adaptee calls, converting arguments, return values, units and errors). You reach for it when you cannot or should not change either side: the Adaptee is third-party, legacy, or shared by other callers, and the Client is already written against the Target. It is a retrofit pattern — applied after the fact to reconcile a mismatch. Key benefits: reuse without modification, the Open/Closed principle, and one isolated place to change when the vendor changes.

code

pseudocode · 18 lines
pseudocode
// Target — what the client is coded against
interface Shape { render() }

// Adaptee — existing, useful, wrong interface, cannot be changed
class LegacyRectangle { draw(x1, y1, x2, y2) { ... } }

// Adapter — implements Target, delegates to Adaptee, translates data
class RectangleAsShape implements Shape {
  constructor(legacy, box) { this.legacy = legacy; this.box = box }
  render() {
    this.legacy.draw(this.box.left, this.box.top,
                     this.box.left + this.box.width,
                     this.box.top  + this.box.height)
  }
}

// Client never mentions LegacyRectangle
fun paint(shapes: List<Shape>) { for (s in shapes) s.render() }

go deeper

for a junior

State the intent in one sentence and name the four participants, then give one concrete mismatch (your interface says render(), the library says draw(x,y,w,h)).

for a middle

Add the object-vs-class adapter distinction, note that the Target must be owned by the client side, and mention translating errors and data, not just method names.

for a senior

Frame it as a retrofit pattern, contrast with Bridge/Decorator/Facade/Proxy, and discuss what happens when the adaptee cannot fully satisfy the target contract.

for a principal

Scale it up: adapters as the boundary of a hexagonal architecture / anti-corruption layer, who owns the port, contract tests across multiple adapters, and the cost of a lowest-common-denominator interface.

## The problem Two pieces of code do the *same thing* but *say it differently*. Your reporting code calls `render(shape)`; the charting library you bought exposes `draw(x, y, w, h)`. Both draw rectangles. Neither will change: the library is a compiled third-party artifact, and your calling code is spread across fifty call sites (or is itself a framework you don't own). This mismatch is not a *behavior* problem — it is an *interface* problem. **Interface** here means the set of operations a caller may invoke plus their names, parameters, return values and error behavior. In some languages this is a literal `interface`/`protocol`/`trait`; in others it is just "the shape of calls this object supports". The pattern is language-agnostic — it applies wherever one side expects a shape the other doesn't have. ## The intent (GoF wording) > *Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.* Aliases you'll hear: **Wrapper**, **shim**, **translator**, **glue**. ("Wrapper" is ambiguous — Decorator and Proxy also wrap; see below.) ## The four participants | Participant | Role | |---|---| | **Target** | The interface the client is written against, e.g. `Shape.render()`. Owned by *your* side. | | **Client** | Code that only ever talks to the Target. It must not know the Adaptee exists. | | **Adaptee** | The existing, useful implementation with the *wrong* interface, e.g. `LegacyRectangle.draw(x,y,w,h)`. | | **Adapter** | Implements/satisfies Target, holds or extends an Adaptee, and translates. | The direction matters: the Adapter is named for *what it produces* (the Target), not for what it wraps — so `LegacyRectangleShapeAdapter` or, more idiomatically, `LegacyRectangleAsShape`. ## What "translate" actually covers An adapter is rarely a one-line forward. Real adapters convert: - **Method shape** — one Target call may become several Adaptee calls (`open`, `write`, `flush`, `close`), or several Target calls may collapse into one. - **Data model** — a `Shape` object into four primitive coordinates; a vendor DTO into your domain type. - **Units and encodings** — Celsius→Kelvin, cents→decimal, epoch millis→date-time, UTF-8 bytes→text. - **Error vocabulary** — the Adaptee's `SdkHttpException` into your `PaymentUnavailable`. Leaking the Adaptee's exception types through an adapter defeats the entire point, because the client then depends on the Adaptee after all. - **Lifecycle** — pooled connections, disposal, retries. ## When to use it - You want to use an existing class whose interface doesn't match. - You want a reusable class that must cooperate with unforeseen future classes (a **pluggable adapter** — the class defines a narrow port, and adapters are supplied per collaborator). - You must isolate a volatile external dependency behind a stable, owned interface (see also Hexagonal Architecture's *ports and adapters*, and DDD's *anti-corruption layer*). ## When *not* to use it - You control both sides and neither has shipped → just fix the interface. An adapter added "in case" is speculative indirection. - The mismatch is behavioral, not structural (the library simply does the wrong thing). Adapters translate shape, not semantics you don't have. - You need to add behavior to the same interface → that's Decorator. ## Costs One more type, one more hop, one more thing to test; the mapping code is genuinely where bugs hide (unit and null-semantics mistakes). Against that: the client is testable with a fake Target, and a vendor swap touches exactly one class. ## Everyday examples A text-reader over a byte-stream; a collection view over an array; a logging facade routing to whichever logging backend is present; a database driver presenting a standard query interface over a proprietary wire protocol; and physically, a power-plug or USB-C-to-HDMI adapter — the pattern's namesake.

  • Who should own the Target interface — your code or the vendor's SDK?
    Your code. The whole value of the adapter is that the interface is defined by the *consumer's* needs (dependency inversion). If you copy the vendor's interface verbatim as your Target, you've inverted nothing: swapping vendors will still ripple through every client.
  • Is Adapter a compile-time or run-time pattern?
    Both, depending on form. A class adapter (inheritance) is fixed at compile time; an object adapter (composition) picks its adaptee at run time, so one adapter type can serve many adaptee instances or subclasses, and adapters can be chosen by configuration.
  • Does an adapter ever add behavior?
    Legitimately only what translation requires — buffering, caching a handle, converting errors. If it starts adding features while keeping the same interface, it has become a Decorator; if it adds access control or laziness, a Proxy. Naming it honestly matters for maintenance.

A travel power adapter: the laptop charger (adaptee) has the wrong prongs for the wall socket (target). The adapter doesn't change the charger or rewire the hotel — it sits between them and makes the plug fit. It also converts nothing about the electricity's purpose, only its interface.

saying these in an interview costs you the question

  • Saying Adapter adds new functionality — that is Decorator; Adapter only changes the interface.
  • Claiming Adapter simplifies a complex subsystem — that's Facade; Facade invents a new, simpler interface, Adapter conforms to an interface that already exists.
  • Letting the adaptee's exception types or DTOs escape through the adapter, so clients still depend on the vendor.
  • Believing you must modify the adaptee — the point is that you can't or shouldn't.
  • Calling every wrapper class an adapter regardless of intent.

context