Skip to content

Go Embedding — Struct Embedding Interface Embedding and Method Promotion Explained

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Go Embedding. We cover key concepts, practical examples, and best practices to help you master this topic.

Go embedding allows one struct or interface to include another type anonymously, promoting its fields and methods to the embedding type for composition-based code reuse without class inheritance.

What You'll Learn

  • Struct embedding for field/method promotion
  • Interface embedding for composition
  • Overriding promoted methods
  • Embedding vs composition patterns

Why It Matters

Embedding is Go's composition approach. Docker embeds structs for container configurations and driver implementations. Kubernetes uses embedding for resource types (PodSpec, ObjectMeta). DodaZIP embeds archive readers for format support.

Real-World Use

sync.Mutex is embedded in structs for locking. http.Handler is embedded in middleware wrappers. Standard library uses embedding extensively for type composition.

flowchart LR
    A["Embedding"] --> B["Struct"]
    B --> C["Method Promotion"]
    C --> D["Interface"]
    D --> E["Overriding"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Struct Embedding Basics

type Address struct {
    City  string
    State string
    Zip   string
}

type Person struct {
    Name string
    Age  int
    Address  // Embedded — fields promoted
}

func main() {
    p := Person{
        Name: "Alice",
        Age:  30,
        Address: Address{
            City:  "New York",
            State: "NY",
            Zip:   "10001",
        },
    }

    // Promoted fields — accessed directly
    fmt.Println(p.City)   // New York
    fmt.Println(p.State)  // NY
    fmt.Println(p.Zip)    // 10001

    // Also accessible through the embedded type
    fmt.Println(p.Address.City)  // New York
}

Method Promotion

Embedded types' methods are promoted to the embedding type:

type Logger struct{}

func (l Logger) Info(msg string) {
    fmt.Println("[INFO]", msg)
}

func (l Logger) Error(msg string) {
    fmt.Println("[ERROR]", msg)
}

type Server struct {
    Logger  // Embedded — methods promoted
    Host    string
    Port    int
}

func main() {
    s := Server{
        Host: "localhost",
        Port: 8080,
    }

    // Promoted methods
    s.Info("Server starting")
    s.Error("Failed to connect")

    // Also works through embedded type
    s.Logger.Info("Explicit call")
}

Overriding Promoted Methods

Define a method with the same name to override:

type Base struct{}

func (b Base) Greet() string {
    return "Hello from Base"
}

type Derived struct {
    Base
}

func (d Derived) Greet() string {
    return "Hello from Derived"
}

func main() {
    d := Derived{}

    // Overridden method
    fmt.Println(d.Greet())  // Hello from Derived

    // Original still accessible!
    fmt.Println(d.Base.Greet())  // Hello from Base
}

Embedding Interfaces

Interfaces can embed other interfaces:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// Embedded interfaces
type ReadWriter interface {
    Reader
    Writer
}

type Closer interface {
    Close() error
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// Implementing the composed interface
type File struct{}

func (f File) Read(p []byte) (int, error)  { return 0, nil }
func (f File) Write(p []byte) (int, error) { return len(p), nil }
func (f File) Close() error                 { return nil }

func main() {
    var rw ReadWriter = File{}
    var rwc ReadWriteCloser = File{}
    _ = rw
    _ = rwc
}

Embedding with Initialization

type Config struct {
    Host string
    Port int
}

type Database struct {
    Config        // Embedded
    Name     string
    PoolSize int
}

func main() {
    // Initialize with struct literal
    db := Database{
        Config: Config{
            Host: "localhost",
            Port: 5432,
        },
        Name:     "myapp",
        PoolSize: 10,
    }

    fmt.Println(db.Host)      // localhost (promoted)
    fmt.Println(db.Port)      // 5432 (promoted)
    fmt.Println(db.Name)      // myapp
}

Embedding Multiple Types

type Writer struct{}

func (w Writer) Write(data []byte) (int, error) {
    return len(data), nil
}

type Flusher struct{}

func (f Flusher) Flush() {
    fmt.Println("Flushed")
}

type Closer struct{}

func (c Closer) Close() error {
    fmt.Println("Closed")
    return nil
}

// Embedding multiple types
type WriteFlushCloser struct {
    Writer
    Flusher
    Closer
}

func main() {
    wfc := WriteFlushCloser{}
    wfc.Write([]byte("data"))
    wfc.Flush()
    wfc.Close()
}

Name Conflicts

When embedded types have the same field or method name:

type A struct {
    Name string
}

type B struct {
    Name string
}

type C struct {
    A
    B
}

func main() {
    c := C{}
    // c.Name  // Ambiguous! Compile error
    fmt.Println(c.A.Name)  // Must use explicit path
    fmt.Println(c.B.Name)  // Must use explicit path
}

Common Patterns

Mutex Embedding

type Counter struct {
    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.Lock()
    defer c.Unlock()
    c.value++
}

func (c *Counter) Value() int {
    c.Lock()
    defer c.Unlock()
    return c.value
}

Decorator Pattern

type LoggedReader struct {
    io.Reader
    LogFile *os.File
}

func (lr LoggedReader) Read(p []byte) (int, error) {
    n, err := lr.Reader.Read(p)
    if n > 0 {
        lr.LogFile.Write(p[:n])
    }
    return n, err
}

func main() {
    file, _ := os.Open("data.txt")
    log, _ := os.Create("read.log")
    reader := LoggedReader{Reader: file, LogFile: log}
    io.Copy(os.Stdout, reader)
}

Embedding vs Composition

// Composition (named field)
type Car struct {
    Engine Engine  // "Has-a" relationship
    Wheels []Wheel
}

// Embedding (anonymous field)
type Motorcycle struct {
    Engine         // Is-like-a relationship
    Wheels  []Wheel
}

// Embedding promotes methods, composition requires delegation
type Engine struct {
    Horsepower int
}

func (e Engine) Start() {
    fmt.Println("Engine started")
}

func main() {
    // Embedding — methods promoted
    m := Motorcycle{Engine: Engine{Horsepower: 100}}
    m.Start()  // Promoted from Engine

    // Composition — must call explicitly
    c := Car{Engine: Engine{Horsepower: 200}}
    c.Engine.Start()  // Must use field name
}

Common Mistakes

1. Name Collisions

type A struct{ X int }
type B struct{ X int }
type C struct {
    A
    B
}
// c.X — compile error, ambiguous

2. Forgetting to Initialize Embedded Type

type Server struct {
    Logger
    Host string
}

s := Server{Host: "localhost"}
// s.Info("test")  // Panic! Logger is nil struct

// Fix — initialize
s = Server{
    Logger: Logger{},
    Host:   "localhost",
}
s.Info("test")  // Works

3. Pointer vs Value Embedding Confusion

type Config struct{ Port int }

type Server struct {
    *Config  // Pointer embedding
}

s := Server{Config: &Config{Port: 8080}}
fmt.Println(s.Port)  // 8080 — works because Config is initialized

4. Overriding Without Access to Original

type Base struct{}
func (b Base) Method() { fmt.Println("base") }

type Derived struct{ Base }
func (d Derived) Method() {
    // Can't call b.Base.Method() if you want the original
    // Unless Base exposes it differently
}

5. Interface Embedding with Conflicting Methods

type A interface { Method() string }
type B interface { Method() int }  // Same name, different return
// type C interface { A; B }  // Compile error — conflicting methods

Practice Questions

1. What is embedding in Go?

Including one type within another anonymously (without a field name). The embedded type's fields and methods are promoted to the embedding type, accessible as if defined on the embedding type.

2. How do you resolve name conflicts in embedded types?

Use explicit field access: c.A.Name instead of c.Name when both A and B have a Name field. Go refuses to promote ambiguous fields.

3. What's the difference between embedding a struct and using a named field?

Embedding promotes methods and fields. Named fields require explicit access. Embedding is for "is-like-a" relationships; named fields are for "has-a" relationships.

4. Can you embed a pointer type?

Yes. type Server struct { *Config } embeds a pointer to Config. The embedded type must be initialized before use, or methods on nil pointer will panic.

Challenge: Create a MeasuredReader that embeds io.Reader and tracks bytes read and read duration.

Solution
package main

import (
    "fmt"
    "io"
    "strings"
    "sync/atomic"
    "time"
)

type MeasuredReader struct {
    io.Reader
    bytesRead int64
    duration  int64 // nanoseconds
}

func (mr *MeasuredReader) Read(p []byte) (int, error) {
    start := time.Now()
    n, err := mr.Reader.Read(p)
    elapsed := time.Since(start)

    atomic.AddInt64(&mr.bytesRead, int64(n))
    atomic.AddInt64(&mr.duration, elapsed.Nanoseconds())

    return n, err
}

func (mr *MeasuredReader) Stats() (bytes int64, avgDuration time.Duration) {
    bytes = atomic.LoadInt64(&mr.bytesRead)
    totalNS := atomic.LoadInt64(&mr.duration)
    if bytes > 0 {
        avgDuration = time.Duration(totalNS / bytes)
    }
    return
}

func main() {
    data := strings.NewReader("Hello, Measured Reader!")
    mr := &MeasuredReader{Reader: data}

    result, _ := io.ReadAll(mr)
    fmt.Printf("Read: %s\n", string(result))

    bytes, avg := mr.Stats()
    fmt.Printf("Total bytes: %d\n", bytes)
    fmt.Printf("Avg time per byte: %v\n", avg)
}

Expected output:

Read: Hello, Measured Reader!
Total bytes: 22
Avg time per byte: ~XXns

FAQ

{{< faq question="Is embedding the same as inheritance?" >}} No. Inheritance is an "is-a" relationship with polymorphism and virtual dispatch. Embedding is composition with method promotion. You can override methods but there's no dynamic dispatch or polymorphic behavior. {{< /faq >}}

{{< faq question="Can I embed multiple types with the same method?" >}} Yes, but calling the method is ambiguous — compile error. Use explicit path to resolve: s.TypeA.Method() or s.TypeB.Method(). {{< /faq >}}

{{< faq question="Does embedding work with non-struct types?" >}} Yes. You can embed any named type: type MyReader struct { io.Reader; buf [1024]byte }. The embedded type's methods are promoted if they don't conflict. {{< /faq >}}

{{< faq question="Can I embed a type from another package?" >}} Yes. The embedded type must be exported (capital letter). type MyWriter struct { io.Writer } embeds the exported Writer interface from the io package. {{< /faq >}}

{{< faq question="How do I initialize an embedded struct?" >} Use a struct literal with the embedded type's name: s := Server{Logger: Logger{}, Host: "localhost"}. Both named and embedded fields are initialized this way. {{< /faq >}}

Try It Yourself

package main

import "fmt"

type Button struct {
    label string
}

func (b Button) Click() {
    fmt.Println(b.label, "clicked!")
}

type TextField struct {
    value string
}

func (t TextField) Input(text string) {
    t.value = text
    fmt.Println("Text set to:", text)
}

type Form struct {
    Button
    TextField
    Name string
}

func main() {
    form := Form{
        Button:    Button{label: "Submit"},
        TextField: TextField{},
        Name:      "Login Form",
    }

    form.Click()           // Promoted from Button
    form.Input("admin")    // Promoted from TextField
    fmt.Println(form.Name) // Own field
}

Expected output:

Submit clicked!
Text set to: admin
Login Form

What's Next

Now that you understand embedding, learn about error handling — Go's approach to explicit error management.

Topic Description Link
Go Error Handling Error interface, wrapping, sentinel errors {{< ref "13-error-handling" >}}
Go Interfaces Interface types, satisfaction, assertions {{< ref "11-interfaces" >}}
Rust Error Handling Compare with Rust's Result type Rust

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go