This guide covers golang embedding and go struct embedding: anonymous fields, promoted fields and promoted methods (the spec’s terminology), and golang type embedding in interfaces. It contrasts composition with inheritance, when to use a named field instead, and separates embed struct golang patterns from //go:embed file embedding. For interfaces and method sets in general, see interfaces in Go; for struct basics, see structs in Go.
Tested on: Go go1.24.4 linux/amd64; kernel 6.14.0-37-generic.
What is embedding in Go?
Embedding means declaring a field by type only—no explicit field name. The embedded type sits inside the outer struct or interface and participates in promotion: exported members of the inner type can be reached through the outer value using the usual selector rules. In practice, embedding is mainly for composition and reuse of behavior (methods) and data (fields) without copy-pasting boilerplate.
Struct embedding vs //go:embed
These are easy to confuse by name only:
- Struct / type embedding — a language feature: anonymous field in a
structor embedded interface in aninterfacetype. //go:embed— a compiler directive (withembed) that packs files into the binary at build time.
They do not interact. This article is only about type embedding; for assets in binaries, read the embed package docs separately.
Basic embedding: promoted fields and methods
An anonymous field is declared with its type name only (no separate field identifier). The Go spec explains when inner exported fields and methods promote to the outer type: you can often write outer.Field as shorthand for outer.InnerType.Field when there is no ambiguity. Unexported fields do not promote to callers outside the defining package.
The Book example embeds Author. AuthorName is promoted, so both b.AuthorName and b.Author.AuthorName refer to the same field.
package main
import "fmt"
type Author struct {
AuthorName string
AuthorAge int
}
type Book struct {
Title string
Author
}
func main() {
b := Book{
Title: "Book1",
Author: Author{
AuthorName: "author1",
AuthorAge: 19,
},
}
fmt.Println(b.AuthorName, b.Author.AuthorName, b.AuthorAge)
}You should see author1 twice and 19 once—the age is also promoted as b.AuthorAge.
Promoted methods
Methods on the embedded type promote the same way as fields, so call sites can use s.Log(...) instead of s.Logger.Log(...) when Logger is embedded.
package main
import "fmt"
type Logger struct{ prefix string }
func (l Logger) Log(msg string) {
fmt.Println(l.prefix, msg)
}
type Service struct {
name string
Logger
}
func main() {
s := Service{name: "api", Logger: Logger{"[api]"}}
s.Log("starting")
}You should see [api] starting from the promoted Log method on Service.
Composition, inheritance, and the inner value
Embedding is composition: the outer struct contains a value of the inner type. It does not create Java/C++-style subtype polymorphism or a this chain up a superclass. Promotion is syntactic convenience, not “the outer type is a kind of inner.”
A function that expects an Inner still needs an Inner value. You pass the embedded field explicitly; the outer struct is not substitutable for the inner type.
package main
type Inner struct{ v int }
type Outer struct{ Inner }
func takesInner(Inner) {}
func main() {
var o Outer
takesInner(o.Inner)
}If you try to pass o where an Inner is required, the program does not compile:
package main
type Inner struct{ v int }
type Outer struct{ Inner }
func takesInner(Inner) {}
func main() {
var o Outer
takesInner(o)
}cannot use o (variable of struct type Outer) as Inner value in argument to takesInnerThe embedded value is still a real field named by its type (Inner, Logger, …). Use outer.Inner or &outer.Inner whenever you need the inner value as Inner or *Inner, or to call an inner method hidden by an outer method of the same name.
Field name conflicts and ambiguity
If two embedded types at the same level expose the same promoted name, or a top-level field collides with a promoted name, a bare selector like c.X can be ambiguous—the compiler rejects it and you must qualify by the embedded type name.
This program does not compile (ambiguous selector):
package main
type A struct{ X int }
type B struct{ X int }
type C struct {
A
B
}
func main() {
var c C
_ = c.X
}ambiguous selector c.X(Exact path and line depend on your file; the compiler reports an ambiguous selector when promotion cannot pick A.X vs B.X.)
Embedding with pointer fields
You can embed a pointer type (*T) instead of a value T. Promotion and method sets follow the same rules as for a named pointer field: if you embed *Counter, a nil embedded pointer means promoted methods that use the receiver can panic if they dereference without a nil check. Prefer embedding a value T when you want a zero value that is always usable, or ensure the pointer is initialized before use.
package main
import "fmt"
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }
func (c *Counter) String() string { return fmt.Sprint(c.n) }
type Widget struct {
*Counter
label string
}
func main() {
w := Widget{Counter: &Counter{}, label: "w"}
w.Inc()
fmt.Println(w.String(), w.label)
}You should see 1 and w. If Widget were constructed with Counter: nil, calling w.Inc() would panic—initialize embedded pointers.
Embedding and method “overriding”
If the outer type defines a method with the same name as a promoted method, the outer method is what you get on a value of the outer type: it does not behave like virtual dispatch in OO languages. You can still call the inner implementation via outer.Inner.Method() when you need it.
package main
import "fmt"
type Inner struct{}
func (Inner) M() { fmt.Println("inner") }
type Outer struct{ Inner }
func (Outer) M() { fmt.Println("outer") }
func main() {
var o Outer
o.M()
o.Inner.M()
}You should see outer then inner. The outer M hides the promoted M for selector o.M(); it is not dynamic override.
Interface embedding and satisfying interfaces
Combining method sets
An interface type can embed other interfaces without listing each method again; the result’s method set is the union of the embedded interfaces (same idea as io.ReadWriter). See also interfaces in Go.
package main
import "fmt"
type Reader interface{ Read() string }
type Writer interface{ Write(string) }
type ReadWriter interface {
Reader
Writer
}
type rwImpl struct{}
func (rwImpl) Read() string { return "data" }
func (rwImpl) Write(s string) {
fmt.Println("wrote", s)
}
func use(rw ReadWriter) {
fmt.Println(rw.Read())
rw.Write("x")
}
func main() {
use(rwImpl{})
}You should see data then wrote x. A concrete type satisfies ReadWriter only if it implements every method from both embedded interfaces.
Structs and interface satisfaction via promotion
If the embedded type’s method set already includes the methods of interface I, those methods count toward the outer struct’s method set when promotion applies. So a wrapper can satisfy io.Reader by embedding *bytes.Reader and forwarding Read without defining Read on the wrapper—use this only when the API is intentional and clear.
package main
import (
"bytes"
"fmt"
"io"
)
type LogReader struct {
*bytes.Reader
tag string
}
func NewLogReader(b []byte, tag string) LogReader {
return LogReader{Reader: bytes.NewReader(b), tag: tag}
}
func main() {
r := NewLogReader([]byte("hello"), "req-1")
var _ io.Reader = r
buf := make([]byte, 3)
n, _ := r.Read(buf)
fmt.Println(r.tag, string(buf[:n]), n)
}You should see req-1, hel, and 3: Read comes from the embedded *bytes.Reader, while tag belongs to LogReader only.
Choosing embedding vs a named field
When embedding helps
Good fits: shared behavior (logging, mutex, small helpers), wrapper types that extend a base implementation, composition of two cohesive values, and less boilerplate when promotion makes call sites clearer. Prefer embedding when the embedded type is a stable, documented part of the outer type’s identity.
When a named field is clearer
Embedding too many types—or types with very wide exported surfaces—can hide where fields and methods come from and can widen your exported API by accident. If readers should always see the relationship explicitly, use a named field (logger Logger) instead of an anonymous one.
| Situation | Prefer |
|---|---|
| Want promoted fields/methods for shorter call sites | Embedding (anonymous field) |
| Want explicit ownership and “has-a” visibility in the API | Named field |
| Want a clear boundary between sub-objects | Named field |
| Want to combine interface contracts | Embedded interfaces in an interface type |
| Want class-style inheritance | Not Go’s model; use composition and small interfaces |
Common mistakes with embedding
- Treating embedding as inheritance or expecting virtual method dispatch.
- Embedding many unrelated types and obscuring the public API.
- Hitting ambiguous selector errors and not knowing to qualify by embedded type name.
- Expecting child/parent polymorphism like Java or C++.
- Embedding
*Twithout initializing and then calling promoted methods. - Accidentally widening the exported API because embedded exported names promote.
- Confusing struct embedding with
//go:embedfile embedding.
Summary
Golang embedding centers on anonymous embedded fields: promoted fields and promoted methods follow the Go spec selector rules, giving you composition and shorter selectors—not inheritance. Struct embedding differs from //go:embed, which is about files in the binary. Use named fields when explicit structure matters; use interface embedding to combine contracts. Watch ambiguity, nil embedded pointers, and outer methods with the same name as hiding, not virtual override. Pair this page with interfaces in Go and structs in Go when you design larger APIs.
References
- The Go Programming Language Specification: Selectors
- The Go Programming Language Specification: Struct types
- Effective Go: Embedding
embedpackage (//go:embed)- Interfaces in Go
- Structs in Go

