Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
15: Embedding Structs for Composition
I’ve seen this happen with almost every developer I've mentored who comes from a Java or Python background: they see struct embedding in Go and immediately think, "Oh, great, inheritance!" They start treating the embedded struct as a base class and expect the outer struct to "be" an instance of the inner one. This is the fastest way to run into a wall of compiler errors in Go.
The "Is-A" Relationship Myth
In a class-based language, if a VideoFile inherits from MediaFile, a VideoFile is a MediaFile. You can pass it into any function that expects a MediaFile. In Go, embedding is not inheritance; it is composition. When you embed a type, you aren't creating a child class; you're just telling Go to "promote" the fields and methods of the inner struct to the outer one for convenience.
Let me show you exactly where this falls apart. Look at this code:
type MediaFile struct {
Name string
Size int
}
type VideoFile struct {
MediaFile // Embedding
Duration int
}
func PrintName(m MediaFile) {
fmt.Println("File name is:", m.Name)
}
func main() {
v := VideoFile{
MediaFile: MediaFile{Name: "vacation.mp4", Size: 1024},
Duration: 120,
}
// This will fail to compile!
PrintName(v)
}
The compiler will scream at you: cannot use v (type VideoFile) as type MediaFile in argument to PrintName. Even though VideoFile embeds MediaFile, it is not a MediaFile. It just happens to contain one.
Promoted Fields: Composition by Proxy
If it's not inheritance, why do we do it? Because it saves us from writing tedious "proxy" code. Without embedding, if you wanted to access the name of the media file through the video file, you'd have to write v.MediaFile.Name. That gets exhausting when you have multiple levels of nesting.
When you embed MediaFile into VideoFile, Go "promotes" those fields. You can now access v.Name directly. It feels like inheritance, but under the hood, Go is just doing the v.MediaFile.Name lookup for you. I personally find this a much cleaner way to build complex objects without the rigid, fragile hierarchies that plague traditional OOP.
Here is how you actually handle the "is-a" problem. You use an interface. If PrintName accepted an interface that defined a GetName() method, then both MediaFile and VideoFile could satisfy it.
type Namer interface {
GetName() string
}
func (m MediaFile) GetName() string {
return m.Name
}
func PrintName(n Namer) {
fmt.Println("File name is:", n.GetName())
}
// Now PrintName(v) works because VideoFile "inherits"
// the GetName method from the embedded MediaFile.
What Happens When Methods Clash
One thing that often trips people up is "shadowing." Since Go promotes methods from the embedded struct, what happens if both the outer and inner structs have a method with the same name? Go follows a simple rule: the outer struct always wins.
Imagine your MediaFile has a method called Describe(). If you also define Describe() on VideoFile, calling v.Describe() will execute the version on VideoFile. The embedded method isn't gone; it's just hidden. You can still reach it explicitly by calling v.MediaFile.Describe().
I usually recommend using this to your advantage. Use the embedded struct for the "default" behavior, and override specific methods in the outer struct only when the specialized type needs to do something different. It's a powerful way to share logic without locking yourself into a strict class tree.
📋 Practical Task
Implementing a Game Character Stat System
You are building a simple RPG. Instead of creating separate structs for every character type, you want to use composition to handle shared statistics. Your goal is to implement a system where different character types share a base set of stats but can override specific behaviors.
Requirements:
- Create a
BaseStatsstruct with fields forHealthandStrength, and a methodGetPowerLevel() intthat returns the sum of health and strength. - Create a
Warriorstruct that embedsBaseStatsand adds aStaminafield. - Create a
Magestruct that embedsBaseStatsand adds aManafield. - The
Mageneeds to override theGetPowerLevel()method. For mages, the power level should be(Health + Strength) + (Mana / 2). - In your
mainfunction, instantiate both aWarriorand aMage, and print their power levels to prove that the warrior uses the base logic while the mage uses the overridden logic.
There are no comments for now.