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

Terraform Developer Toolchain – Hands-On Lab Manual

DOWNLOAD – HERE Write -> Validate -> Test -> Lint -> Secure -> Document -> Cost -> Automate -> Govern Framework What | Why | When |…

Read More

Kafka Master Tutorials Series: 7 Topics, Partitions, Consumers, Consumer Groups & Lag

Developer Planning Guide for Correct Mapping, Scaling, Reliability and Performance Audience: Developers, students, freshers, architects, platform engineersTraining context: Confluent Kafka ClusterGoal: Remove confusion around how Kafka Topics, Partitions, Producers, Consumers,…

Read More

Kafka Master Tutorials Series: 6 – Kafka Consumer Deep Dive

Consumer Groups, Parallelism, Offsets, Rebalancing, Failover, Consumption Patterns and Production Tuning Audience: Students and freshers with no previous Kafka experienceGoal: Start with “What is a consumer?” and finish with…

Read More

Redis Tutorials: A Complete Fundamental Turorials

From Fundamentals to Production-Grade Caching, Sessions, Pub/Sub, Counters, Locks and Failure Handling 1. What is Redis? Redis is a high-performance, primarily in-memory data store. The easiest mental…

Read More

Kafka Master Tutorials Series: 5 – Deep Dive Into Kafka Producers

From send() to Broker ACK: Keys, Partitions, Batching, Retries, Reliability, Latency and Performance Tuning Audience: Students and freshers with no prior Kafka experienceGoal: Build from producer fundamentals to production-grade Kafka producer…

Read More

Kafka Master Tutorials Series: 4 – Confluent Cloud Kafka — Production Checklist

1. Cluster Architecture Recommended architecture: 2. Capacity Planning Confluent recommends monitoring cluster load closely. Sustained load around 70–80% is a reason to consider adding CKUs, while above…

Read More