This is one of the most important mindset changes when coming to Go from Java, C++, C#, or Python.
The key idea is simple:
Inheritance says: “I am a type of something.”
Composition says: “I contain something and use its functionality.”
Go deliberately does not have classical inheritance such as:
class Dog extends Animal
Instead, Go encourages you to build larger types by combining smaller types.
1. What is composition?
Suppose we have a Car.
A car needs an engine.
In an inheritance-heavy design, you might be tempted to think about parent and child classes.
With composition, you simply say:
Car HAS an Engine
Instead of:
Car IS an Engine
That distinction is the heart of composition.
flowchart LR
Car[Car] -->|has an| Engine[Engine]
Engine --> Start["Start()"]
Car --> Drive["Drive()"]
2. Complete Go Example
Copy-paste this entire program into main.go.
package main
import "fmt"
// Engine is a small independent type.
// It knows how to start itself.
type Engine struct {
HorsePower int
}
// Start belongs to Engine.
func (e Engine) Start() {
fmt.Println("Engine started")
}
// Car is made by COMPOSING smaller things together.
//
// A Car HAS an Engine.
// Car does not inherit from Engine.
type Car struct {
Brand string
Engine Engine
}
// Drive belongs to Car.
//
// Car can use the behavior of the Engine
// because Car contains an Engine.
func (c Car) Drive() {
c.Engine.Start()
fmt.Printf(
"%s is driving with %d HP\n",
c.Brand,
c.Engine.HorsePower,
)
}
func main() {
// Create an Engine.
engine := Engine{
HorsePower: 150,
}
// Put the Engine inside the Car.
car := Car{
Brand: "Toyota",
Engine: engine,
}
// Car uses its composed Engine internally.
car.Drive()
}
Output
Engine started
Toyota is driving with 150 HP
3. What exactly happened?
Start with this:
type Engine struct {
HorsePower int
}
Engine is an independent type.
It has its own behavior:
func (e Engine) Start() {
fmt.Println("Engine started")
}
Then we create another type:
type Car struct {
Brand string
Engine Engine
}
This line is the important part:
Engine Engine
It means:
A
Carcontains anEngine.
That is composition.
Conceptually:
Car
├── Brand
└── Engine
└── HorsePower
The Car can therefore use its engine:
c.Engine.Start()
and access its data:
c.Engine.HorsePower
4. Why does Go prefer composition?
Because large inheritance hierarchies can become tightly coupled.
Imagine something like:
Vehicle
↓
Car
↓
ElectricCar
↓
AutonomousElectricCar
↓
DeliveryAutonomousElectricCar
Changes in a parent class can affect many child classes.
Composition usually creates smaller independent pieces:
flowchart TD
Car --> Engine
Car --> GPS
Car --> Battery
Car --> Brakes
A Car becomes a combination of components.
Car
├── Engine
├── GPS
├── Battery
└── Brakes
That is generally easier to understand and change.
5. Classical Inheritance vs Go Composition
| Classical Inheritance | Go Composition |
|---|---|
| Child extends parent | Struct contains another type |
Dog extends Animal | Dog contains useful components |
| Usually represents is-a | Usually represents has-a |
| Behavior inherited from parent | Behavior is used through contained types |
| Can create deep class hierarchies | Usually creates small independent components |
| Parent and child can become tightly coupled | Components are easier to replace |
| Common in Java/C++/C# | Preferred approach in Go |
The easiest memory trick is:
| Relationship | Think |
|---|---|
| Inheritance | IS-A |
| Composition | HAS-A |
For our example:
Car HAS-A Engine
So composition is natural.
6. How do I recognize composition in Go?
Look for one struct containing another type.
type Engine struct {
HorsePower int
}
type Car struct {
Engine Engine
}
Read it naturally:
Carhas anEngine.
Then you usually access it like:
car.Engine.Start()
This explicit style is very common and very easy to understand.
7. Where should I use composition?
Use composition when one thing uses, contains, owns, or depends on another thing.
For example:
Car HAS Engine
Computer HAS CPU
Order HAS Customer
Server HAS Logger
User HAS Address
Game HAS Player
App HAS Database
These naturally become structs containing other structs.
For example:
type Address struct {
City string
}
type User struct {
Name string
Address Address
}
That is composition too.
8. When should I use it?
Whenever you find yourself thinking:
“This struct needs functionality/data provided by another thing.”
For example:
type Server struct {
Logger Logger
}
Then:
server.Logger.Log()
Rather than creating strange inheritance relationships.
9. The important mindset
If you come from Java, you might initially think:
What should my parent class be?
In Go, try changing the question to:
What small pieces does my type need?
Then combine those pieces.
For example:
Instead of:
Animal
↓
Dog
Think:
Dog
├── Name
├── Legs
└── Behavior
Go encourages building programs from small cooperating types, rather than large inheritance trees.
10. Don’t confuse composition with copying
When we write:
type Car struct {
Engine Engine
}
we are not copying the Engine code into Car.
We are saying:
Car owns/contains an Engine value.
So:
car.Engine.Start()
means:
Car
↓
Engine
↓
Start()
11. One-line definition to remember
Composition in Go means building a type by combining other types instead of inheriting from a parent class.
The basic pattern is simply:
type SmallThing struct {
}
type BiggerThing struct {
Small SmallThing
}
Read it as:
BiggerThing HAS-A SmallThing
Final mental model
flowchart LR
A["Classical OOP"] --> B["Parent"]
B --> C["Child inherits behavior"]
D["Go"] --> E["Small Type A"]
D --> F["Small Type B"]
E --> G["Composed Type"]
F --> G
When writing Go, prefer thinking:
Build with pieces
rather than:
Build with parent/child classes
So for this example:
type Car struct {
Engine Engine
}
is the core idea.
Car has an Engine → composition.
Not:
Car extends Engine → inheritance.