← Back to Go Course | Chapter 7: Interfaces | Lesson 7 of 7

Interface Composition

Interface composition is like building a bigger job description by stapling two smaller ones together -- a type has to be able to do everything both lists require.

Embedding Interfaces

An interface can embed another interface simply by naming it inside its own body, which pulls in all of that interface's method requirements without repeating their signatures.

Example: Embedding Interfaces

markup
package main

import "fmt"

type Reader interface {
	Read() string
}

type Writer interface {
	Write(string)
}

type ReadWriter interface {
	Reader
	Writer
}

type Buffer struct {
	data string
}

func (b *Buffer) Read() string    { return b.data }
func (b *Buffer) Write(s string)  { b.data += s }

func main() {
	var rw ReadWriter = &Buffer{}
	rw.Write("hello")
	fmt.Println(rw.Read())
}

The Standard Library's io.ReadWriter

The standard library follows this exact pattern with io.ReadWriter, which embeds both io.Reader and io.Writer -- any type implementing both Read and Write methods automatically satisfies io.ReadWriter with no extra code.

Note: This composition pattern is exactly how the real io.ReadWriter type is defined.

Example: The Standard Library's io.ReadWriter

markup
package main

import (
	"bytes"
	"fmt"
)

func main() {
	var buf bytes.Buffer // *bytes.Buffer satisfies io.ReadWriter
	buf.Write([]byte("composed interfaces"))
	fmt.Println(buf.String())
}

Satisfying a Composed Interface

To satisfy a composed interface, a type simply needs to implement every method required by each embedded interface -- there's nothing special about composition from the implementing type's point of view; it's just a larger method set to fulfill.

Example: Satisfying a Composed Interface

markup
package main

import "fmt"

type Named interface {
	Name() string
}

type Aged interface {
	Age() int
}

type Person interface {
	Named
	Aged
}

type Employee struct {
	name string
	age  int
}

func (e Employee) Name() string { return e.name }
func (e Employee) Age() int     { return e.age }

func main() {
	var p Person = Employee{name: "Nikhil", age: 29}
	fmt.Println(p.Name(), p.Age())
}
Common Mistakes
  1. Manually re-listing every method from another interface instead of simply embedding that interface by name.
  2. Assuming a composed interface changes how satisfaction works -- a type must still implement every method from all embedded interfaces, nothing is optional.
  3. Creating deeply nested composed interfaces that become hard to trace back to their required method set.
Chapter Summary
  • Interfaces can embed other interfaces, combining their method sets into one larger interface.
  • io.ReadWriter is a standard-library example: it embeds both io.Reader and io.Writer.
  • A type must implement every method from all embedded interfaces to satisfy the composed interface.
  • Composition lets you build larger contracts from small, well-understood pieces instead of duplicating method lists.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.