What Is Composition Over Inheritance? The Built-In Compose Way Explained

Table of Contents

If you’ve been writing object-oriented code for a while, you’ve probably used inheritance a lot. It feels natural. You create a base class, extend it, override a few methods, and move on.

But as projects grow, inheritance often becomes hard to manage. Classes get tightly coupled. Changes ripple through the codebase. Small tweaks break unexpected things.

This is where Composition Over Inheritance comes in.

In this post, we’ll break down what Composition Over Inheritance really means, why it matters, and how it’s used naturally in modern Kotlin development, especially with Jetpack Compose. 

What Does “Composition Over Inheritance” Mean?

Composition Over Inheritance is a design principle that says:

Prefer building classes by combining smaller, reusable components instead of extending base classes.

In simpler terms:

  • Inheritance says “is a”
  • Composition says “has a”

Instead of forcing behavior through class hierarchies, you compose behavior by using other objects.

A Simple Real-World Example

Think of a smartphone.

A smartphone has a camera, battery, speaker, and screen.

It does not inherit from Camera, Battery, or Speaker.

That’s composition.

If you used inheritance here, the design would fall apart fast.

The Problem With Inheritance

Inheritance looks clean at first, but it comes with hidden costs.

Example Using Inheritance (Problematic)

Kotlin
open class Vehicle {
    open fun move() {
        println("Vehicle is moving")
    }
}

open class Car : Vehicle() {
    override fun move() {
        println("Car is driving")
    }
}

class ElectricCar : Car() {
    override fun move() {
        println("Electric car is driving silently")
    }
}

At first glance, this seems fine.

But now imagine:

  • You want a flying car
  • You want a boat-car
  • You want a self-driving electric truck

Your inheritance tree explodes.

Changes to Vehicle affect every subclass. You’re locked into decisions you made early, often before requirements were clear.

This is exactly what Composition Over Inheritance helps you avoid.

Composition Over Inheritance Explained With Kotlin

Let’s rewrite the same idea using composition.

Create Small, Focused Behaviors

Kotlin
interface Engine {
    fun move()
}
Kotlin
class GasEngine : Engine {
    override fun move() {
        println("Driving using gas engine")
    }
}
Kotlin
class ElectricEngine : Engine {
    override fun move() {
        println("Driving silently using electric engine")
    }
}

Each class has one clear responsibility.

Compose the Behavior

Kotlin
class Car(private val engine: Engine) {

    fun drive() {
        engine.move()
    }
}

Now the Car has an engine, instead of being forced into a rigid hierarchy.

Use It

Kotlin
fun main() {
    val electricCar = Car(ElectricEngine())
    electricCar.drive()

    val gasCar = Car(GasEngine())
    gasCar.drive()
}

Output:

Driving silently using electric engine
Driving using gas engine

This is Composition Over Inheritance in action.

Why Composition Over Inheritance Is Better

Here’s why modern Kotlin developers strongly prefer this approach.

1. Less Coupling

Your classes depend on interfaces, not concrete implementations.

2. Easier Changes

You can swap behaviors without rewriting class hierarchies.

3. Better Testability

You can inject fake or mock implementations easily.

4. Cleaner Code

Smaller classes. Clear responsibilities. Fewer surprises.

Composition Over Inheritance in Jetpack Compose

Jetpack Compose is built almost entirely on Composition Over Inheritance.

That’s not an accident.

Traditional UI (Inheritance-Heavy)

Kotlin
class CustomButton : Button {
    // override styles, behavior, states
}

This leads to rigid UI components that are hard to reuse.

Compose Way (Composition First)

Kotlin
@Composable
fun MyButton(
    text: String,
    onClick: () -> Unit
) {
    Button(onClick = onClick) {
        Text(text)
    }
}

Here’s what’s happening:

  • MyButton is not extending Button
  • It uses Button
  • Behavior is passed in, not inherited

This is Composition Over Inheritance at the UI level.

Why Compose Feels Easier to Work With

Compose avoids deep inheritance trees entirely.

Instead:

  • UI is built from small composable functions
  • Each function does one thing
  • You combine them like building blocks

That’s composition by design.

Delegation: Kotlin’s Built-In Support for Composition

Kotlin makes Composition Over Inheritance even easier with delegation.

Example Using Delegation

Kotlin
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) {
        println(message)
    }
}

class UserService(private val logger: Logger) : Logger by logger

Now UserService automatically uses ConsoleLogger’s implementation without inheritance.

This keeps your code flexible and clean.

When Should You Still Use Inheritance?

Inheritance is not evil. It’s just often overused.

Inheritance works best when:

  • There is a true “is-a” relationship
  • The base class is stable
  • You control both parent and child classes

If those conditions are missing, Composition Over Inheritance is usually the safer choice.

Conclusion

Let’s wrap it up.

  • Composition Over Inheritance means building behavior using objects, not class hierarchies
  • Kotlin makes composition easy with interfaces and delegation
  • Jetpack Compose is a real-world example of this principle done right
  • Composition leads to flexible, testable, and maintainable code

If you’re writing Kotlin today, especially with Compose, you’re already using Composition Over Inheritance whether you realized it or not.

And once you start designing with it intentionally, your code gets simpler, not harder.

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!