I changed a formatter from a concrete parameter to an interface and expected the call to behave like a generic wrapper around the value. The program worked, but looking at method dispatch made the cost and behavior less mysterious.

Conceptually, an interface value is two machine words: information describing the dynamic type and a word referring to the dynamic value. For a non-empty interface, the type information also leads to the method table needed to dispatch calls. An empty interface needs no method table, but it still carries dynamic type and value.

Copying an interface copies those two words, not the object they describe:

var src io.Reader = bytes.NewBufferString("hello")
dst := src

Both interfaces refer to the same buffer. Reading through dst advances the state later observed through src; the assignment did not clone the buffer. This is easy to miss because the interface itself is a small value while the dynamic value may be shared mutable state.

Calls through an interface use the implementation selected by the dynamic type. Go’s implicit satisfaction means the concrete type does not declare which interfaces it implements; the compiler checks the method set at assignment or call sites. Pointer and value method sets still matter, so T and *T need not satisfy the same interface.

The two-word representation does not promise a two-word operation. A call still dispatches to the dynamic type’s method, and whether a value escapes depends on how the compiler can follow it through that call. Interface size alone says little about allocation.

An interface-to-interface assignment can change the static contract while preserving the same dynamic value. A type assertion asks whether that dynamic type satisfies another interface; it does not convert the object into a new representation. I use the two-result form when failure is ordinary:

c, ok := w.(io.Closer)

The one-result form panics on failure, which belongs only where the invariant is truly guaranteed.

I kept the interface where it represented a real behavioral boundary and restored the concrete parameter in the private formatter. Interfaces are contracts, not performance annotations or automatic copies of the values inside them.