Go Tutorials: Composition Instead of Classical Inheritance

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 Car contains an Engine.

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 InheritanceGo Composition
Child extends parentStruct contains another type
Dog extends AnimalDog contains useful components
Usually represents is-aUsually represents has-a
Behavior inherited from parentBehavior is used through contained types
Can create deep class hierarchiesUsually creates small independent components
Parent and child can become tightly coupledComponents are easier to replace
Common in Java/C++/C#Preferred approach in Go

The easiest memory trick is:

RelationshipThink
InheritanceIS-A
CompositionHAS-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:

Car has an Engine.

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.

Related Posts

Go Tutorials: Go Methods

Go methods become much easier once you understand one idea: A method is simply a function attached to a type. And the important rule is: A receiver…

Read More

Go Tutorials: Modules, Packages, Subpackages, Submodules, and Workspaces

This tutorial gives you one complete mental model for: The most important idea is: And sometimes: These two designs are very different. 1. What is a Go…

Read More

Go Tutorials: Numeric Types Beginner Tutorial

Go has four main families of numeric types: The simplest way to remember them is: Type family Stores Example int Whole numbers, positive or negative -10, 0,…

Read More

Go Tutorials: Go Testing, Benchmarking, and Profiling

These three topics become much easier once you separate the questions they answer: Topic Main question Main Go tool Result Testing Does my code work correctly? go…

Read More

Go Tutorials: Generics

1. What are Generics? Generics let you write one piece of code that works with multiple types while keeping Go’s compile-time type safety. Suppose you want a…

Read More

Go Concurrency Management Made Simple

This tutorial covers: The goal is: One concept → one purpose → one complete runnable example. Every example is independent. Save any example as: and run: 0….

Read More