Inheritance & Interfaces

Reviewed & published by Brayan K

Kotlin is a modern, concise language that supports object-oriented design through inheritance and interfaces — but with safe defaults, since classes are final unless you opt in with open.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll build class hierarchies, override methods polymorphically, and implement interfaces and abstract classes.

What You'll Learn in This Lesson

1️⃣ Inheritance and Overriding

In Kotlin, classes and methods are final by default. To allow a subclass, mark the class open; to allow overriding a method, mark it open too. A subclass inherits with : and replaces behaviour with override.

// Classes are FINAL by default. Mark a class 'open' to allow subclassing,
// and 'open' a function to allow overriding it.
open class Animal(val name: String) {
    open fun speak(): String = "...some sound..."
    fun describe(): String = "$name says ${speak()}"
}

// A subclass uses ':' to inherit, and 'override' to replace a method.
class Dog(name: String) : Animal(name) {
    override fun speak(): String = "Woof"
}

class Cat(name: String) : Animal(name) {
    override fun speak(): String = "Meow"
}

fun main() {
    val animals = listOf(Dog("Rex"), Cat("Lily"), Animal("Mystery"))
    for (a in animals) {
        println(a.describe())   // polymorphism: the right speak() is chosen
    }
}

This is polymorphism: the loop calls describe() on each animal, and the correct speak() runs depending on the actual type.

2️⃣ Interfaces and Abstract Classes

An interface defines a contract and may include default implementations. An abstract class can hold state and mix concrete with abstract members. A class can implement many interfaces but extend only one class.

// An interface declares a contract; classes implement it.
interface Drivable {
    val maxSpeed: Int                 // abstract property
    fun drive(): String               // abstract method
    fun honk(): String = "Beep!"      // default implementation
}

// An abstract class can hold state AND abstract members.
abstract class Vehicle(val wheels: Int) {
    abstract fun kind(): String
}

class Car : Vehicle(4), Drivable {    // extend a class AND an interface
    override val maxSpeed = 120
    override fun kind() = "Car"
    override fun drive() = "Driving a ${kind()} at $maxSpeed km/h on $wheels wheels"
}

fun main() {
    val car = Car()
    println(car.drive())              // -> Driving a Car at 120 km/h on 4 wheels
    println(car.honk())               // default from interface -> Beep!
}

Car extends an abstract Vehicle and implements Drivable — and gets honk() free from the interface's default.

Your turn. Replace the TODO, then run and compare.

// 🎯 YOUR TURN — replace each TODO then run main()

open class Shape {
    open fun area(): Double = 0.0
}

// 1) Make Square inherit from Shape and override area().
class Square(val side: Double) : Shape() {
    TODO fun area(): Double = side * side   // 👉 replace TODO with 'override'
}

class Circle(val radius: Double) : Shape() {
    override fun area(): Double = Math.PI * radius * radius
}

fun main() {
    val shapes: List<Shape> = listOf(Square(3.0), Circle(1.0))
    for (s in shapes) {
        println("%.2f".format(s.area()))
    }
    // ✅ Expected:
    //    9.00
    //    3.14
}

Mini-Challenge: A Notification System

Define one interface and two implementations, then treat them polymorphically through the interface type.

// 🎯 MINI-CHALLENGE: a notification system.
// Save as Notify.kt and run main().
//
// 1. Define interface Notifier with fun send(message: String): String.
// 2. Make class EmailNotifier implement it, returning
//    "Email: <message>".
// 3. Make class SmsNotifier implement it, returning
//    "SMS: <message>".
// 4. In main(), put both in a List<Notifier>, loop over them,
//    and print send("Hello") for each.
//
// ✅ Example output:
//    Email: Hello
//    SMS: Hello

fun main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Inheritance

ConceptKotlin Syntax
Open a classopen class Animal
Subclassclass Dog : Animal()
Overrideoverride fun speak()
Interfaceinterface Drivable
Abstract classabstract class Vehicle
Call parentsuper.speak()

🎉 Lesson Complete!

Practice quiz

By default, a Kotlin class is…

  • open for subclassing
  • abstract
  • final and cannot be inherited
  • an interface

Answer: final and cannot be inherited. Classes are final by default; you must mark them open to allow subclassing.

Which keyword allows a class or method to be inherited or overridden?

  • open
  • public
  • virtual
  • extend

Answer: open. open allows a class to be subclassed and a method to be overridden.

What keyword must a subclass put before a method that replaces a base method?

  • replace
  • super
  • override
  • open

Answer: override. Replacing an inherited method requires the override keyword.

How does a subclass declare it inherits from a base class?

  • With the keyword extends
  • With a colon, e.g. class Dog : Animal()
  • With the keyword implements
  • With the keyword inherits

Answer: With a colon, e.g. class Dog : Animal(). Kotlin uses a colon: class Dog(name: String) : Animal(name).

What does polymorphism mean in the Animal example?

  • All animals share one speak()
  • The correct overridden speak() is chosen at runtime
  • Animals cannot be put in a list
  • speak() must be final

Answer: The correct overridden speak() is chosen at runtime. Calling speak() runs the right override for each object's actual type at runtime.

Which statement about interfaces is true?

  • They can store mutable state in backing fields
  • A class can implement only one interface
  • They can include default method implementations
  • They cannot declare properties

Answer: They can include default method implementations. Interface methods may provide a default body that implementers inherit.

How many classes can a Kotlin class extend?

  • Exactly one
  • As many as needed
  • None
  • Up to two

Answer: Exactly one. A class can extend only one class but implement many interfaces.

What can an abstract class do that an interface cannot?

  • Declare abstract methods
  • Be implemented by a class
  • Hold state in backing fields with a constructor
  • Have a name

Answer: Hold state in backing fields with a constructor. An abstract class can hold state and have a constructor; interfaces cannot store fields.

How do you call the parent's version of a method?

  • parent.method()
  • base.method()
  • this.method()
  • super.method()

Answer: super.method(). super.method() invokes the superclass implementation.

When a Car implements Drivable, where does honk() come from if Car doesn't define it?

  • The interface's default implementation
  • A compile error
  • The Vehicle class
  • It returns null

Answer: The interface's default implementation. Car inherits honk() free from Drivable's default implementation.

Continue this course

Frequently asked questions

Why are Kotlin classes final by default?

Kotlin makes classes and methods final (non-inheritable, non-overridable) unless you explicitly mark them open. This follows the principle 'design for inheritance or prohibit it' — uncontrolled subclassing can break a class in subtle ways. Requiring open forces you to consciously decide which classes and methods are meant to be extended, leading to safer, more intentional hierarchies. To allow subclassing, write open class; to allow overriding a method, write open fun.

What is the difference between an interface and an abstract class?

An interface defines a contract — a set of methods and properties a class promises to provide — and can include default method implementations, but it cannot hold mutable state (no backing fields). An abstract class can hold state, have constructors, and mix concrete and abstract members, but a class can extend only one of them. Use an interface to describe a capability that many unrelated classes can share, and an abstract class when you want to share state and base behaviour within a single hierarchy. A class can implement many interfaces but extend only one class.

What does polymorphism mean here?

Polymorphism means a single piece of code can work with objects of many related types, and the correct overridden behaviour is chosen at runtime. In the example, looping over a List<Animal> and calling speak() invokes Dog's version for a Dog and Cat's for a Cat, even though the loop only knows they are Animals. This lets you write general code against a base type and have specialised subclasses fill in the details.

Can interfaces have default implementations?

Yes. An interface method can provide a default body, as honk() does in the example, so implementing classes inherit that behaviour for free and only override it when they need something different. Interfaces can also declare abstract properties (without backing fields) and properties with custom getters. What they cannot do is store state in a backing field, which is the main thing reserved for classes.