📺 Analogy
A TV remote (the abstraction) and the TV itself (the implementation) evolve separately. A fancy universal remote works with a Sony or a Samsung; a basic remote works with both too. You don’t build a SonyFancyRemote and a SamsungBasicRemote for every pairing — the remote talks to any TV through a shared interface.
The problem
Two things vary at once: what you’re drawing (circle, square) and how it’s rendered (vector, ASCII). Model them with inheritance and you need a type for every combination — CircleVector, CircleASCII, SquareVector… an N×M explosion. Bridge separates the two hierarchies: the abstraction holds a reference to an implementation interface, so each side grows on its own.
Structure
classDiagram
class Circle {
-renderer Renderer
-radius float64
+Draw()
+Resize()
}
class Renderer {
<<interface>>
+RenderCircle(radius)
}
class VectorRenderer { +RenderCircle() }
class ASCIIRenderer { +RenderCircle() }
Circle o--> Renderer : bridges to
Renderer <|.. VectorRenderer
Renderer <|.. ASCIIRenderer
Idiomatic Go
In Go, Bridge is just composition: the abstraction holds the implementation as an interface field. Any shape pairs with any renderer — no combinatorial types. Edit and Run:
package main
import "fmt"
// Implementor: the "how it's rendered" dimension.
type Renderer interface {
RenderCircle(radius float64) string
}
type VectorRenderer struct{}
func (VectorRenderer) RenderCircle(r float64) string {
return fmt.Sprintf("vector circle, radius %.1f", r)
}
type ASCIIRenderer struct{}
func (ASCIIRenderer) RenderCircle(r float64) string {
return fmt.Sprintf("ascii circle, ~%d chars wide", int(r*2))
}
// Abstraction: the "what it is" dimension, bridged to a Renderer.
type Circle struct {
renderer Renderer
radius float64
}
func (c Circle) Draw() string { return c.renderer.RenderCircle(c.radius) }
func (c *Circle) Resize(f float64) { c.radius *= f }
func main() {
// any shape × any renderer — no CircleVector / CircleASCII explosion
for _, r := range []Renderer{VectorRenderer{}, ASCIIRenderer{}} {
c := Circle{renderer: r, radius: 5}
c.Resize(2)
fmt.Println(c.Draw())
}
}
🐹 Bridge is almost invisible in Go
Because Go pushes you toward “composition over inheritance,” Bridge often appears without anyone naming it — you just hold an interface field. The skill is recognizing the two independent dimensions early and refusing to let them multiply. It resembles Strategy (inject behavior) but Strategy swaps an algorithm, while Bridge separates a whole abstraction hierarchy from an implementation hierarchy.
In the standard library
io.Writerbridges writers (fmt.Fprintf, loggers) from concrete sinks (file, buffer, socket).database/sqlbridges the SQL API from pluggable drivers.log/slogbridges the logging front-end from itsHandlerback-ends.
Pitfalls
⚠️ Don't bridge a single dimension
If only one axis ever varies, a Bridge is over-design — a plain interface (or Strategy) says it better. The pattern earns its complexity only when both sides genuinely change independently. Introduce it when the second dimension actually appears, not before.
When to use it — and when not
✅ Reach for it when
- Two (or more) dimensions vary independently — e.g. shape × renderer, message × transport.
- You'd otherwise get a class explosion (CircleSVG, CircleCanvas, SquareSVG, …).
- You want to swap the implementation at runtime.
⛔ Think twice when
- Only one dimension actually varies — a single interface (or Strategy) is enough.
- The abstraction and implementation never change independently.
Related patterns
Convert the interface of a type into another interface clients expect, letting otherwise-incompatible types work together.
BEHAVIORAL StrategyDefine a family of interchangeable algorithms, encapsulate each one, and select which to use at runtime.
CREATIONAL Abstract FactoryProvide an interface for creating families of related objects without specifying their concrete types.
Check your understanding
Score: 0 / 31. What does Bridge decouple?
Bridge splits a design along two axes and connects them with an interface, so you can combine any abstraction with any implementation without subclassing every pairing.
2. What explosion does Bridge prevent?
Without Bridge, M shapes × N renderers means M×N concrete types. With Bridge it's M + N — each side grows independently.
3. How does Bridge differ from Adapter?
Adapter is reactive — make X fit interface Y after the fact. Bridge is proactive design — keep the abstraction and implementation apart so both can evolve.