---
title: 'Interface Embedding'
source: 'https://academia.sh/en/courses/go-type-system/interface-embedding'
course: 'Interfaces, Errors and Generics'
language: en
updated: '2026-09-14T10:59:34+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures with reflect that interface embedding combines two contracts at the declaration level in the source and adds no new method'
    - 'Asks the compiler to find the minimum method count a type must write to satisfy a composite interface'
    - 'Shows that a type satisfying a composite interface also satisfies each of the embedded interfaces'
    - 'Runs proof that combining a contract does not combine implementation — two separate types can each satisfy a separate contract while neither satisfies the composite one'
---

# Interface Embedding

Embedding combines two interfaces into a single composite contract in the source, and this combination adds no new method at all: the same contract, built once by embedding and once rewritten by hand, wants the same two methods. The minimum method count a type must write to satisfy the composite interface equals the total method count of the embedded interfaces, no more, no fewer. Limiting measurement: embedding only combines the contract, not the implementation — two separate types can each satisfy their own separate contract, yet neither satisfies the composite contract.

The previous lesson saw a type able to satisfy more than one contract at the same time, but
each contract stood separately in the source. When a type needs to both be able to store and
be closable, a way is needed to refer to these two requirements with a single name. Embedding
provides that way: an interface can take in other interfaces into its own body just by writing
their names. This is unlike any of the bindings in the previous three lessons — explicitly
written in the source, and so the clearest example of the **declaration** class.

Of the sixteen bindings in the first lesson, only four were in a declaration, and two of those
were already about embedding: `Embedded` itself and the compile-time proof that verified it.
This lesson grows those two lines and measures embedding's own rules — what it combines, how
much it combines, and where it stops. Three measurements proceed in this order: first it is
confirmed that embedding really adds no new method; then how many methods are needed at
minimum to satisfy the composite contract is found; finally, where this combination stops —
in the contract or in the implementation — is tested.

## Combining Contracts in the Source

An interface embedding is nothing more than writing another interface's name inside an
interface body. The result is a new interface wanting the full set of methods from the
embedded interfaces. The syntax is short: no field name is placed in front of the embedded
interface, only its own name is written as a single line — the same writing rule embedding
borrows from struct embedding, a line that looks like an unnamed field but is actually a type
name. The following measurement builds the same contract two ways — once by embedding, once by
rewriting it by hand — and counts the method count both want with `reflect`.

```go
// equivalence.go — a contract built with embedding asks for the same method count as one written by hand
package main

import (
	"fmt"
	"reflect"
)

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }

// Embedding: two interface names are written into the embedding interface's body.
type ReadWriterEmbedded interface {
	Reader
	Writer
}

// The same contract, rewritten by hand.
type ReadWriterManual interface {
	Read() string
	Write(m string)
}

func main() {
	a := reflect.TypeOf((*ReadWriterEmbedded)(nil)).Elem()
	b := reflect.TypeOf((*ReadWriterManual)(nil)).Elem()

	fmt.Println("method count wanted by embedding  :", a.NumMethod())
	fmt.Println("method count wanted by hand-writing:", b.NumMethod())
	for i := 0; i < a.NumMethod(); i++ {
		fmt.Println("  method:", a.Method(i).Name)
	}
}
```

```
method count wanted by embedding  : 2
method count wanted by hand-writing: 2
  method: Read
  method: Write
```

`ReadWriterEmbedded`'s source carries only two names (`Reader`, `Writer`), and writes no
method signature at all. But when `reflect` is asked, it reports wanting exactly two methods,
`Read` and `Write`, just like `ReadWriterManual`, which writes both methods by hand. Embedding
is a shortcut — it lets you write less in the source — but the contract it produces is
**identical, byte for byte**, to the one written by hand. This is an extension of the embedding
concept from the Go Fundamentals course's Structs lesson: there a struct carried another
struct's field and method names without writing them; here an interface carries another
interface's method names without writing them. Both are called by the same word
(**embedding**) because both use the same mechanism: writing one name brings everything that
name carries along with it.

**IN10.** The syntax `reflect.TypeOf((*T)(nil)).Elem()` is the standard way to obtain an
interface type itself (not an interface *value*) as a `reflect.Type` — `(*T)(nil)` produces a
pointer value, `.Elem()` descends to the interface type that pointer points to. No real `T`
value is ever constructed throughout this measurement; only the type itself is queried.

Embedding is not limited to two interfaces here; an embedded interface can itself embed
another interface, and the chain can stretch several levels. The following measurement goes one
level deeper by embedding `ReadWriter` with a third interface (`Closer`) and tests that the
total method count is still summed correctly.

```go
// chain.go — two-level embedding, the total method count still adds up correctly
package main

import (
	"fmt"
	"reflect"
)

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

type ReadWriter interface {
	Reader
	Writer
}

type FullSource interface {
	ReadWriter
	Closer
}

func main() {
	t := reflect.TypeOf((*FullSource)(nil)).Elem()
	fmt.Println("two-level embedding, total method count:", t.NumMethod())
	for i := 0; i < t.NumMethod(); i++ {
		fmt.Println("  method:", t.Method(i).Name)
	}
}
```

```
two-level embedding, total method count: 3
  method: Close
  method: Read
  method: Write
```

`FullSource` embeds `ReadWriter`, and `ReadWriter` itself embeds `Reader` and `Writer` — two
levels. The total method count is again three, the sum of the three individually embedded
methods (`Read`, `Write`, `Close`). Adding more levels does not change the summing rule: each
level carries every name below it upward, no name is ever lost, and no name is ever counted
twice.

This clarifies what someone reading `FullSource` needs to see: the source carries only two
lines (`ReadWriter`, `Closer`), yet the contract wants three methods. Reaching this count from
the source is not possible without opening the intermediate level (`ReadWriter`) — the reader
has to follow the whole chain. Embedding shortens the source, but it does not reduce the
reading burden by the same amount; a short source can still carry a long contract.

## How Many Methods Have to Be Written

Since embedding adds no method, the minimum method count a type must write to satisfy a
composite interface should be the sum of the embedded interfaces' methods — no more, no fewer.
`reflect` counted this by asking an interface **type**; the following measurement asks the same
question by testing a **type against an interface definition**, taken straight from the
compiler itself. Both should arrive at the same number: the count `reflect` reports is the
smallest count the compiler accepts.

```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
}
```

```go
// how-many-methods.go — the minimum method count needed to satisfy a composite interface is measured
package main

import "fmt"

func main() {
	common := `package trial

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }

type ReadWriter interface {
	Reader
	Writer
}

`
	zeroMethods := common + `type Candidate struct{}

var _ ReadWriter = Candidate{}
`
	oneMethod := common + `type Candidate struct{}
func (Candidate) Read() string { return "" }

var _ ReadWriter = Candidate{}
`
	twoMethods := common + `type Candidate struct{}
func (Candidate) Read() string { return "" }
func (Candidate) Write(m string) {}

var _ ReadWriter = Candidate{}
`
	fmt.Println("does it compile with zero methods:", compiles(zeroMethods))
	fmt.Println("does it compile with one method   :", compiles(oneMethod))
	fmt.Println("does it compile with two methods  :", compiles(twoMethods))
}
```

```
does it compile with zero methods: false
does it compile with one method   : false
does it compile with two methods  : true
```

The zero-method candidate does not compile, because both are missing. The one-method candidate
(only `Read`) does not compile either, because `Write` is still missing — satisfying `Reader`
does not mean satisfying `ReadWriter`; embedding does not let one interface stand in for the
other, it wants both together. The two-method candidate compiles, and this is exactly the
number `reflect` counted in the previous section. Three candidates test a single threshold (two
methods) from both directions: one short is not enough, the exact count is enough.

This threshold follows the same axis as the small-interface measurement in the third lesson:
the more methods an interface wants, the heavier the load on the type that satisfies it.
Embedding does not **reduce** the method count here; it only makes it easier to refer to two
separate contracts with a single name. The cost of satisfying `ReadWriter` is the sum of the
costs of satisfying `Reader` and `Writer` separately — embedding offers no shortcut that lowers
this sum, it only gathers the naming into one place.

**IN11.** All three candidates carry the same name (`Candidate`) and are tested against the
same `ReadWriter`; the only thing that changes is `Candidate`'s own method set. This makes the
three measurements comparable — the difference sits in a single variable, everything else stays
fixed. The same discipline could also be tested with a fourth candidate — one writing three
methods, two matching the names `ReadWriter` wants and a third an extra method. A type like
this would still compile, because interface satisfaction is a **subset** relationship: as long
as a type's method set covers what the interface wants, having more is not a problem.

## Limiting Measurement: The Contract Combines, the Implementation Does Not

Embedding combines two contracts in the source, but this combination gives no
**implementation** guarantee. A type satisfying the composite interface also satisfies the two
embedded interfaces separately — that direction is easy. But the reverse is not true: two
separate types can each satisfy two separate contracts separately, and neither satisfies the
composite contract. The threshold measurement in the previous section asked "how many methods
are enough" over a single type; this measurement asks the same question over more than one
type, and shows there is no sharing at all between types.

```go
// two-directions.go — the two directions of satisfying a composite contract are tested separately
package main

import "fmt"

type Reader interface{ Read() string }
type Writer interface{ Write(m string) }

type ReadWriter interface {
	Reader
	Writer
}

type File struct{}

func (File) Read() string  { return "content" }
func (File) Write(m string) {}

type ReadOnly struct{}

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

type WriteOnly struct{}

func (WriteOnly) Write(m string) {}

func main() {
	var f any = File{}
	_, reads := f.(Reader)
	_, writes := f.(Writer)
	_, composite := f.(ReadWriter)
	fmt.Println("forward direction, is File a Reader     :", reads)
	fmt.Println("forward direction, is File a Writer     :", writes)
	fmt.Println("forward direction, is File a ReadWriter :", composite)

	var reader any = ReadOnly{}
	var writer any = WriteOnly{}
	_, readerOk := reader.(Reader)
	_, writerOk := writer.(Writer)
	_, readerCompositeOk := reader.(ReadWriter)
	_, writerCompositeOk := writer.(ReadWriter)
	fmt.Println("reverse direction, is ReadOnly a Reader        :", readerOk)
	fmt.Println("reverse direction, is WriteOnly a Writer       :", writerOk)
	fmt.Println("reverse direction, is ReadOnly a ReadWriter    :", readerCompositeOk)
	fmt.Println("reverse direction, is WriteOnly a ReadWriter   :", writerCompositeOk)
}
```

```
forward direction, is File a Reader     : true
forward direction, is File a Writer     : true
forward direction, is File a ReadWriter : true
reverse direction, is ReadOnly a Reader        : true
reverse direction, is WriteOnly a Writer       : true
reverse direction, is ReadOnly a ReadWriter    : false
reverse direction, is WriteOnly a ReadWriter   : false
```

In the forward direction, a single type (`File`) satisfies all three interfaces — a type
satisfying the composite contract automatically also satisfies its parts, because the
composite contract's method set is a superset of the parts. `File` having written its `Read`
and `Write` methods is enough on its own; it does not need to write different code three times
to separately satisfy `Reader`, `Writer`, and `ReadWriter`.

The reverse direction is different: `ReadOnly` satisfies only `Reader`, `WriteOnly` only
`Writer`, and both correctly. But **neither** satisfies `ReadWriter` — not `ReadOnly`, not
`WriteOnly`. The two contracts being satisfied separately does not mean the composite contract
is also satisfied; that requires a single type to write **both** methods together. Even if
both `ReadOnly` and `WriteOnly` exist in the program, a place wanting `ReadWriter` accepts
neither of them — there is no such thing as their "sum" in the type system.

**IN12.** `ReadOnly` and `WriteOnly` together, "somewhere," satisfying both `Read` and `Write`
carries no meaning anywhere in the program — Go's type system offers no mechanism that
automatically merges two separate types' methods. Embedding only combines the **interface
definition**; a third type wanting to combine two separate concrete types' behavior has to
either write its own methods or take both concrete types into itself through struct embedding,
as in the K01 Structs lesson.

This distinction explains the basic difference between struct embedding and interface
embedding. When a struct embeds another struct, the embedded type's real value — with its own
fields and methods — sits concretely inside the outer struct; that was the reason the K01
Structs lesson's embedded methods could be called without being written on the outer type. When
an interface embeds another interface, there is no value at all involved, only a list of
signatures is merged. Interface embedding carries no value, it only carries a request; who
satisfies that request is a separate question, and the answer to that question can only be
given by a single concrete type's own methods (or that type's own struct embeddings). Both
kinds of embedding share the same word because both rest on the same idea — writing a name
and gaining everything it carries — but one carries a value while the other carries only a
contract.

## Summary

- Interface embedding is nothing more than writing other interfaces' names into an interface's
  body; the binding stands explicitly in the source, the clearest example of the declaration
  class in this course.
- A contract built with embedding wants, at the `reflect` level, exactly the same method count
  as a contract rewriting the same methods by hand — embedding adds no new method.
- The minimum method count a type must write to satisfy a composite interface equals the sum of
  the embedded interfaces': one short is not enough, the exact count is enough.
- A type satisfying the composite interface also satisfies each of the embedded interfaces, but
  not the reverse: two separate types can satisfy two separate contracts separately while
  neither satisfies the composite contract.
- Interface embedding carries no value, only a contract; struct embedding (the K01 Structs
  lesson) carries a real value — the same word, two separate mechanisms.

## Next Step

In this lesson, the party defining the contract acted **before** the type that would satisfy
it: `Reader` and `Writer` were already there before `File` was written. But the third lesson's
`Storer`, and the first lesson's `Short`, never appeared in the source of the type that
satisfied them — the party defining the contract was the party consuming it. The difference
between these two patterns is not an accident, it is a choice: sometimes the producing party
writes the contract (this lesson), sometimes the consuming party does (the first and third
lessons). The next lesson turns this choice into a design decision: who should define an
interface, the producing package or the consuming side, and does this have a measurable
counterpart?
