---
title: 'The Interface Concept'
source: 'https://academia.sh/en/courses/go-type-system/interface-concept'
course: 'Interfaces, Errors and Generics'
language: en
updated: '2026-09-14T10:59:34+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures that a type implicitly satisfies an interface and that this satisfaction is written nowhere in the source'
    - "Builds and runs the frame that sorts sixteen bindings into declaration, use site, and value classes, producing the course's binding 4/4/8 split"
    - 'Shows that the small-interface principle is not advice but a measurable relationship between method count and the number of types satisfying it'
    - 'Runs proof that a compile-time conformance proof moves the same binding into a declaration, and that the compiler stays silent when that proof is missing'
---

# The Interface Concept

4 of sixteen bindings are established in a declaration, 4 at a use site, 8 in the value — the course's binding split is produced here. The same type satisfies four interfaces and its source has zero references to any of them; the small-interface principle is not advice here but an observation measured by the count of satisfying types falling as method count rises. Limiting measurement: a compile-time conformance proof moves that same binding into the declaration, and the compiler stays silent when the proof is missing.

The Go Fundamentals course closed by asking who decides a name's visibility, a dependency's
version, and the enforcement of a formatting rule — all three answers were the language
itself: exporting depends on a letter's case, version selection on the lowest request written
in the module file, formatting rule on the formatter itself. This lesson carries the same
question one step further in. When a type satisfies an interface, there is no single line
that says so — not in the interface's definition, not in the type's own source. The compiler
knows this binding; someone reading the source cannot see it. So where is this binding
established?

This course asks that question sixteen times — across interfaces, error values, generics —
and each time writes the answer into one of three places: is the binding explicitly written
in the source (**declaration**), does the compiler establish it at the point a value is used
(**use site**), or is the binding carried inside the value itself at runtime (**value**)? This
lesson counts these three places for the first time and produces the number the rest of the
course will refer back to.

## Where a Binding Is Established

An interface defines a method set and does nothing on its own — it carries no field, produces
no behavior. A type **satisfies** an interface if it meets that entire method set with its own
methods. Satisfaction being **implicit** means that no word in the type's source declares this.
A type can even satisfy an interface its own author never heard of; the only condition is that
the method signatures match.

Satisfaction itself is never written in the source. But a **proof that verifies** this
satisfaction can be added to the source — and then the binding moves into the declaration.
This distinction is the topic of the lesson's last section. For now, a verbal definition of
the three classes is enough: which binding stands explicitly in the source, which is
established by the compiler at the point of use, which is carried along with the value at
runtime — the measurement makes this **countable**; we already know this information from the
start.

## The Frame: An Observer That Classifies a Binding

The measurement runs through a small observer that classifies each binding with a single
question. Three package-level counters (`declaration`, `useSite`, `value`) write every
observation to a class; `observe` is the single entry point, `summarize` prints the total.

```go
// binding.go — where is a binding established: in a declaration, at a use site, in the value
package main

import "fmt"

var declaration, useSite, value int

func observe(name, location, evidence string) {
	switch location {
	case "declaration":
		declaration++
	case "use":
		useSite++
	default:
		value++
	}
	fmt.Printf("%-40s %-12s %s\n", name, location, evidence)
}

func summarize() {
	fmt.Printf("binding: %d declaration, %d use site, %d value\n", declaration, useSite, value)
}
```

**IN1.** `observe` takes each line as three parts: a name, a class, a piece of evidence. The
evidence column carries the real value backing the claim — which line belongs to which class
is already known ahead of time; the frame does not produce that decision, it only makes it
**countable**. Keeping the counters at package level means the total stays consistent with the
observations whenever `summarize` is called.

## Sixteen Bindings

This frame asks sixteen bindings in order, inside a single package. The measurement opens with
interface satisfaction, continues with embedding and a compile-time proof, then moves through
the dynamic type of a value placed in the empty interface, the question a type assertion and a
type switch ask of a value, an interface defined on the consumer side, an error value's
implicit satisfaction, a sentinel error's identity, the context a wrapped error carries, and
closes with a generic function's inferred type parameter, a type constraint's declaration, and
the instantiation of a generic type.

```go
// measurement.go — sixteen bindings, split into three locations
package main

import (
	"errors"
	"fmt"
)

type Writer interface{ Write(m string) string }
type Closer interface{ Close() error }

// Short, an interface defined on the consumer side: the producer type never mentions it.
type Short interface{ Write(m string) string }

// Embedded combines two contracts in a declaration.
type Embedded interface {
	Writer
	Closer
}

type File struct{ Name string }

func (f File) Write(m string) string { return f.Name + ":" + m }
func (f File) Close() error          { return nil }

// Compile-time conformance proof: the writer put this in a declaration.
var _ Embedded = File{}

type Record struct{ Number int }

func (r *Record) Failure() string { return "record" }

var ErrNotFound = errors.New("not found")

type FieldError struct{ Field string }

func (e *FieldError) Error() string { return "invalid field: " + e.Field }

func search(found bool) error {
	if !found {
		return fmt.Errorf("search failed: %w", ErrNotFound)
	}
	return nil
}

func validate() error {
	return fmt.Errorf("record could not be validated: %w", &FieldError{Field: "name"})
}

func first[T any](d []T) (T, bool) {
	var zero T
	if len(d) == 0 {
		return zero, false
	}
	return d[0], true
}

type Numeric interface{ ~int | ~float64 }

func sum[T Numeric](d []T) T {
	var t T
	for _, e := range d {
		t += e
	}
	return t
}

type Stack[T any] struct{ items []T }

func (s *Stack[T]) Add(o T) { s.items = append(s.items, o) }
func (s *Stack[T]) Type() string {
	var zero T
	return fmt.Sprintf("%T", zero)
}

func main() {
	fmt.Println("-- where is a binding established --")

	var w Writer = File{Name: "note"}
	observe("a type satisfying an interface", "use", "not written anywhere in the source")
	observe("compile-time conformance proof", "declaration", "var _ Embedded = File{}")
	observe("interface embedding", "declaration", "Embedded = Writer + Closer")

	observe("an interface value's dynamic type", "value", fmt.Sprintf("%T", w))

	c, ok := w.(Closer)
	observe("a type assertion's answer", "value", fmt.Sprint(ok, " ", c != nil))

	var kind string
	switch w.(type) {
	case File:
		kind = "File"
	default:
		kind = "unknown"
	}
	observe("the branch a type switch selects", "value", kind)

	var _ Short = File{}
	observe("an interface defined by the consumer", "use",
		"File satisfies four interfaces, its source has zero references to them")

	var empty any = w
	observe("a value's type in the empty interface", "value", fmt.Sprintf("%T", empty))

	var r *Record
	var h any = r
	observe("an interface carrying a nil pointer", "value", fmt.Sprint("pointer nil=", r == nil, " interface nil=", h == nil))

	var err error = &FieldError{Field: "name"}
	observe("the error interface being satisfied", "use", fmt.Sprintf("%T", err))

	wrapped := search(false)
	observe("a sentinel error's identity", "value", fmt.Sprint(errors.Is(wrapped, ErrNotFound)))
	observe("the text carried by wrapping", "value", wrapped.Error())

	var target *FieldError
	observe("extracting a type from a wrapped chain", "value", fmt.Sprint(errors.As(validate(), &target), " ", target.Field))

	f, found := first([]string{"a", "b"})
	observe("a generic function's type inference", "use", fmt.Sprint(f, " ", found))

	observe("a type constraint's declaration", "declaration", fmt.Sprint("Numeric sum=", sum([]int{1, 2, 3})))

	var stack Stack[float64]
	stack.Add(1.5)
	observe("instantiating a generic type", "declaration", "Stack[float64] -> "+stack.Type())

	summarize()
}
```

```
-- where is a binding established --
a type satisfying an interface           use          not written anywhere in the source
compile-time conformance proof           declaration  var _ Embedded = File{}
interface embedding                      declaration  Embedded = Writer + Closer
an interface value's dynamic type        value        main.File
a type assertion's answer                value        true true
the branch a type switch selects         value        File
an interface defined by the consumer     use          File satisfies four interfaces, its source has zero references to them
a value's type in the empty interface    value        main.File
an interface carrying a nil pointer      value        pointer nil=true interface nil=false
the error interface being satisfied      use          *main.FieldError
a sentinel error's identity              value        true
the text carried by wrapping             value        search failed: not found
extracting a type from a wrapped chain   value        true name
a generic function's type inference      use          a true
a type constraint's declaration          declaration  Numeric sum=6
instantiating a generic type             declaration  Stack[float64] -> float64
binding: 4 declaration, 4 use site, 8 value
```

Of sixteen bindings, **4 are established in a declaration, 4 at a use site, 8 in the value**.
This split is binding for the rest of the course; later lessons will print the lines of their
own slice and refer back to this table in a single sentence. Eight sitting in the value comes
from a Go interface value being a **pair** — it carries both a concrete type and a value
inside it; a type assertion, a type switch, comparing a sentinel error, and extracting a type
from a wrapped chain are all four readings of that same pair. The real subject of this lesson
is the first two lines and the seventh: interface satisfaction itself and its being defined on
the consumer side.

## Implicit Satisfaction: Four Interfaces, Zero References

`File`'s source is three lines: one field, two methods (`Write`, `Close`). These two method
lines satisfy four separate interfaces — `Writer` (wants the `Write` method), `Closer` (wants
the `Close` method), `Embedded` (wants both, by embedding them), and `Short` (wants only
`Write`, a separate definition carrying the same signature as `Writer`). None of these four
interface names ever appear in `File`'s own source — not in the `type File struct` line, not
in the bodies of the `Write` and `Close` methods. Zero references in the source, four
satisfactions.

This is the concrete result of implicit satisfaction: a type can satisfy an interface its own
author never even saw, because the only condition for satisfaction is that the method
signatures match. The `Short` interface was defined **after** `Writer` in `measurement.go`,
and `File` was written with no knowledge of it; despite this, the line `var _ Short = File{}`
compiles. The seventh row of the table reduces this to a single number: four interfaces, zero
references. This observation is the first appearance of consumer-side definition, the topic of
the fifth lesson — there the same idea will turn into a design decision.

## The Small-Interface Principle: Measurement, Not Advice

As an interface's method count rises, the number of types satisfying it is expected to fall —
wanting more methods means a stricter condition. This is the intuition the Design Principles
course's interface-segregation principle rests on; here it is measured not as advice but as a
countable relationship.

```go
// small-interface.go — as an interface's method count grows, does the number of types satisfying it shrink
package main

import "fmt"

type OneMethod interface{ Read() string }
type TwoMethod interface {
	Read() string
	Write(m string)
}
type ThreeMethod interface {
	Read() string
	Write(m string)
	Close() error
}

type ReadOnly struct{}

func (ReadOnly) Read() string { return "read" }

type ReadWrite struct{}

func (ReadWrite) Read() string  { return "read" }
func (ReadWrite) Write(m string) {}

type ReadWriteClose struct{}

func (ReadWriteClose) Read() string   { return "read" }
func (ReadWriteClose) Write(m string) {}
func (ReadWriteClose) Close() error   { return nil }

type WriteOnly struct{}

func (WriteOnly) Write(m string) {}

func main() {
	types := []any{ReadOnly{}, ReadWrite{}, ReadWriteClose{}, WriteOnly{}}

	oneCount, twoCount, threeCount := 0, 0, 0
	for _, t := range types {
		if _, ok := t.(OneMethod); ok {
			oneCount++
		}
		if _, ok := t.(TwoMethod); ok {
			twoCount++
		}
		if _, ok := t.(ThreeMethod); ok {
			threeCount++
		}
	}
	fmt.Println("types satisfying the one-method interface  :", oneCount)
	fmt.Println("types satisfying the two-method interface  :", twoCount)
	fmt.Println("types satisfying the three-method interface:", threeCount)
}
```

```
types satisfying the one-method interface  : 3
types satisfying the two-method interface  : 2
types satisfying the three-method interface: 1
```

**IN2.** Four types were built deliberately: three carry `Read` with growing method sets, the
fourth (`WriteOnly`) never carries `Read` at all and falls outside all three interfaces. Had
this last type not existed, the decline would still show, but the observation "there is also a
type that satisfies none of the interfaces" would be lost — the measurement keeps all four
together for this reason.

Tested against three interfaces, the four types' count falls to **3, 2, 1**: as the method set
grows from one method to three, the number of the same four types able to satisfy it shrinks.
This binding is the same "use site" class seen in the table — each count is established at the
line where a type assertion is called, not in the type's or the interface's own definition. A
small interface means an interface that wants few methods; an interface that wants few methods
means an interface most types satisfy. This is not a design tip, it is the direct result of
the count above.

## Limiting Measurement: Conformance Can Also Be Written in the Declaration

The first row of the sixteen bindings said "use site," and its proof was indirect: it showed
that satisfaction is not written in the source, not where it is established. This section
gives the direct proof: it deliberately breaks conformance and shows exactly where the
compiler stops. The tool is the `compiles` function from the Go Fundamentals course's
`unwritten.go` frame — it writes a piece of source into a temporary package, calls the
compiler, and reads only whether it passed.

```go
// compiler.go — tests from inside the program whether a piece of source compiles
package main

import (
	"os"
	"os/exec"
	"path/filepath"
)

func compiles(source string) bool {
	dir, err := os.MkdirTemp("", "trial")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dir)
	os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module trial\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dir, "code.go"), []byte(source), 0o644)
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

These two pieces of source carry the same half-implementation — a type writes only one of a
two-method interface's methods. The difference is a single line: the second carries a
conformance proof right after the type.

```go
// conformance.go — the compiler stays silent with no proof, stops with a proof in the declaration
package main

import "fmt"

func main() {
	sourceA := `package trial

type FullInterface interface {
	First() string
	Second() string
}

type HalfImplementation struct{}

func (h HalfImplementation) First() string { return "first" }
`
	sourceB := sourceA + `
var _ FullInterface = HalfImplementation{}
`
	fmt.Println("no proof, never used at all    :", compiles(sourceA))
	fmt.Println("with proof, right after the type:", compiles(sourceB))
}
```

```
no proof, never used at all    : true
with proof, right after the type: false
```

`HalfImplementation` is identical in both sources — the `Second` method is missing. In the
first source this type is never used anywhere as `FullInterface`; the compiler stays silent
and the missing method produces no warning at all. In the second source, the only line added
is a conformance proof placed right after the type's definition; the same gap now stops
compilation. The binding moved into the **declaration** in the second source — the proof line
writes into the source not the satisfaction itself, but the **verification** of that
satisfaction, and when that verification breaks, compilation stops right there.

**IN3.** This measurement has a blind spot: `compiles` only reads `cmd.Run()`'s exit status,
never the error text. Making sure the first source really compiles for **no other** reason
than the missing method was set up by hand — the source was written to carry no problem beyond
`Second`. The same discipline holds everywhere in the course a measurement calls the compiler.

A type left without a conformance proof never shows its gap as long as it is never used. This
is the real cost of saying "established at the use site": the binding is only tested the
moment it is used, and never tested if it is not. A proof line closes this risk by moving it
next to the type's own definition.

## Summary

- A type satisfying an interface is implicit: no word declares this in the source; the only
  condition is that the method signatures match.
- Of sixteen bindings, **4 are established in a declaration, 4 at a use site, 8 in the
  value** — the course's binding split was produced in this lesson.
- The `File` type satisfies four interfaces (`Writer`, `Closer`, `Embedded`, `Short`) and its
  own source has zero references to any of the four.
- The small-interface principle is a measurement, not advice: as method count climbs from one
  to two to three, the number of the same four types able to satisfy it falls from three to
  two to one.
- A compile-time conformance proof (`var _ Interface = Type{}`) moves the binding into the
  declaration; without the proof, a missing implementation never shows itself as long as it is
  not used.

## Next Step

In this lesson an interface value's dynamic type appeared a few times — `main.File`, read with
`%T` — but was never dwelt on. The next lesson grows exactly this line: the empty interface,
wanting no method at all, accepts whatever type is placed in it, but the placed type's
information is never lost. Where does that information go, and how does `%T` read it back?
