Structural pattern · Gang of Four · Intermediate

Proxy

Provide a stand-in for another object that implements the same interface, to control access to it.

Structural Intermediate Complete

💳 Analogy

A credit card is a proxy for your bank account. It offers the same operation — “pay” — but in front of the real money it adds authorization, spending limits, and a log of every transaction. The merchant never touches your account directly; they go through the stand-in.

The problem

You want to put a layer in front of an object — to delay its expensive creation, cache its results, check permissions, log calls, or reach it over the network — without changing the object or the code that uses it. A proxy implements the same interface as the real subject and forwards calls, slipping its control logic in between.

Structure

classDiagram
  class Image {
    <<interface>>
    +Display() string
  }
  class RealImage {
    -file string
    +Display() string
  }
  class ProxyImage {
    -file string
    -real RealImage
    +Display() string
  }
  Image <|.. RealImage
  Image <|.. ProxyImage
  ProxyImage o--> RealImage : controls access to

Idiomatic Go

A virtual proxy loads the expensive RealImage only on first use, then caches it. Callers depend only on the Image interface and never know. Edit and Run:

package main

import "fmt"

// Subject interface — callers depend on this.
type Image interface {
	Display() string
}

// RealSubject: expensive to create.
type RealImage struct{ file string }

func NewRealImage(file string) *RealImage {
	fmt.Println("loading", file, "from disk (expensive)")
	return &RealImage{file}
}
func (r *RealImage) Display() string { return "showing " + r.file }

// Proxy: same interface, but defers creation and caches the result.
type ProxyImage struct {
	file string
	real *RealImage
}

func (p *ProxyImage) Display() string {
	if p.real == nil { // virtual proxy: create on first use only
		p.real = NewRealImage(p.file)
	}
	return p.real.Display()
}

func main() {
	var img Image = &ProxyImage{file: "photo.png"}
	fmt.Println("proxy created — nothing loaded yet")

	fmt.Println(img.Display()) // loads now
	fmt.Println(img.Display()) // cached — no second load
}

🐹 Proxy vs Decorator — same shape, different intent

Both wrap an object behind its interface, so in Go they look almost the same. Ask why you’re wrapping: adding features (compression, buffering)? That’s a Decorator. Controlling access (lazy, cache, auth, remote)? That’s a Proxy. Common Go proxies include caching layers, lazy loaders, and httputil.ReverseProxy.

In the standard library

  • net/http/httputil.ReverseProxy — a remote proxy forwarding to a backend.
  • database/sql connection pooling hands you a proxy over a real connection.
  • Lazy/sync.Once-guarded accessors act as virtual proxies for expensive resources.

Pitfalls

⚠️ Hidden cost and hidden state

A proxy makes a cheap-looking call secretly expensive (the first Display() hits the disk) or stateful (caching). That’s the point — but document it, and mind concurrency: a virtual proxy’s lazy init needs a sync.Once or mutex if multiple goroutines might trigger it at once, or you’ll load twice and race.

When to use it — and when not

✅ Reach for it when

  • You want lazy/expensive initialization (create the real thing only on first use).
  • You need to add access control, caching, logging, metrics, or rate-limiting in front of an object.
  • You're accessing a remote object and want a local stand-in (a remote proxy).

⛔ Think twice when

  • You're adding features/behavior, not controlling access — that's a Decorator.
  • The extra indirection buys nothing — don't wrap for the sake of wrapping.

Check your understanding

Score: 0 / 3

1. What is a Proxy's purpose?

A proxy implements the subject's interface and forwards calls, inserting access control such as lazy creation, caching, permissions, or remoting.

2. Proxy and Decorator look structurally identical. What differs?

Both wrap an object behind the same interface. A decorator enriches what the object does; a proxy governs whether/when/how you reach it (lazy, cached, authorized, remote).

3. Which standard-library type is a remote Proxy?

ReverseProxy stands in locally for a remote backend: it presents the http.Handler interface and forwards requests to the real server — a textbook remote proxy.