Stage 1 · Code
Language Depth II
Embedding and Composition
Learn how Go builds bigger behavior from smaller pieces by embedding structs and interfaces, and understand when accepting absolutely any value is helpful versus careless.
Building with Pieces
Go does not chase object-oriented inheritance chains the way some other languages do. Its usual move is simpler: take small pieces that already work, then assemble them into larger shapes. That idea is called composition.
Picture a workshop drawer organizer. You do not melt all the plastic together into one giant blob just because screws and drill bits live in the same drawer. You keep small useful compartments and place them side by side. Go likes code organized that way too.
In Go, larger designs often come from embedding or assembling smaller types and interfaces. The language encourages you to reuse behavior by putting pieces together rather than by creating deep inheritance trees.
Struct Embedding
A struct can contain another struct as an embedded field. When you embed a field, Go promotes that inner field's members to the outer struct. In everyday terms, the outer struct gets to behave as if those fields and methods live directly on it too.
Promotion is about convenient access, not duplication. api.ID looks direct, but the data still lives inside the embedded Base field. Go is simply letting you reach through that field without repeating the field name every time.
Tracing Promoted Fields and Methods
If the outer struct defines a field or method with the same name, that outer member wins for direct access. The embedded one is still there, but you may need to write the full path like api.Base.ID to reach it explicitly.
Interface Embedding
Interfaces can be composed the same way. Instead of writing one giant interface from scratch, you can build a larger one out of smaller behavior contracts. This keeps each small idea legible and reusable.
| Form | What gets combined | Result |
|---|---|---|
| Struct embedding | Fields and methods from an embedded concrete type | The outer struct gets promoted access to them |
| Interface embedding | Method requirements from smaller interfaces | A larger interface is built from reusable behavior pieces |
any and the Empty Interface
The empty interface, written as interface{}, requires zero methods. Because it asks for nothing, every type satisfies it. Modern Go also gives this concept a friendlier alias: any. They mean the same thing.
That power is real, but it is easy to misuse. Accepting any is appropriate at boundaries where the set of possible concrete types is truly wide or unknown: decoding generic JSON, writing a logging helper, storing arbitrary values in a container, or working with APIs designed to accept many unrelated kinds of input.
It becomes a shame in disguise when you use any to avoid designing a better function signature. If your function really expects something that can Read, ask for io.Reader. If it expects items of one type family, consider generics. any should be the honest answer to 'this can genuinely be anything,' not a shortcut around thinking.
If you accept any, you usually push the type-specific work somewhere else. That is sometimes necessary, but it often means the caller loses compile-time guidance and the callee must do extra runtime checking. Prefer a specific interface or a generic type parameter when you already know the shape of the problem.
Quiz
What does embedding `Base` inside `Service` mainly give you?
Practice
Define interfaces `Starter` and `Stopper`, then define `Lifecycle` by embedding those two interfaces. Create a type `Worker` with a `Name` field that implements both methods by returning `nil`. Finally, write a function `CanRestart(l Lifecycle) bool` that always returns `true` after calling `Stop()` and `Start()` in that order.
Summary
- Go prefers composition: building larger designs from smaller working pieces.
- Struct embedding promotes an embedded field's fields and methods to the outer struct.
- Interface embedding builds larger behavior contracts from smaller interfaces.
interface{}andanymean the same thing: a value with no required methods.- Use
anyat genuinely broad boundaries, but prefer a smaller interface or generics when you already know the needed shape.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.