Often several kinds of thing share a lot in common. A savings account and a current account are both accounts. A circle and a square are both shapes. A card payment and a voucher payment are both payments. Inheritance lets a class build on another, reusing its properties and methods and changing only what's different.
Kotlin is deliberately cautious here: classes are final by default, meaning nothing can inherit from them unless you mark them open. This prevents accidental, fragile inheritance chains, a common source of bugs in large codebases. For describing what classes can do, Kotlin prefers interfaces: contracts that any class can promise to fulfil.
In this lesson you'll create open classes and subclasses, override methods and call the parent's version with super, use abstract classes for shared code, define and implement interfaces, and treat different classes the same way through polymorphism.
open classes and override
open class Animal(val name: String) {
open fun sound() = "..."
fun describe() = "$name says ${sound()}"
}
class Dog(name: String) : Animal(name) {
override fun sound() = "Woof"
}openallows a class to be inherited and a method to be overridden.: Animal(name)declares the parent and passes arguments to its constructor.overrideis required when replacing a parent's method. Misspell the name and the compiler tells you, instead of silently creating a new method.describe()isn't open, so subclasses can't change it, but it callssound(), which they can.
super and abstract classes
Inside an overriding method, super.method() calls the parent's version, so you can add to it rather than replace it entirely.
An abstract class can't be created on its own. It can contain normal code plus abstract members with no body, which every subclass must provide:
abstract class Shape {
abstract fun area(): Double
fun describe() = "${this::class.simpleName} with area ${"%.2f".format(area())}"
}Abstract members are automatically open, so subclasses just write override fun area() = ....
Interfaces
An interface describes abilities: a set of functions and properties a class promises to provide.
interface Payable {
val label: String
fun pay(amount: Double): Boolean
fun receipt(amount: Double) = "$label: £$amount" // default implementation
}- A class can implement many interfaces, but extend only one class:
class Card : Payable, Refundable. - Interfaces can include default method implementations, as with
receiptabove. - Properties in interfaces have no stored value; the implementing class provides them.
- Interfaces can't hold state, which keeps them simple and flexible.
Polymorphism and type checks
Code written for a parent type works with every subclass:
val animals: List<Animal> = listOf(Dog("Rex"), Cat("Tom"))
for (a in animals) println(a.describe())Each object runs its own version of sound(). That's polymorphism: one piece of code, many behaviours. When you need subclass-specific features, check with is, after which the value is smart cast: if (a is Dog) a.fetch().
Kotlin Inheritance Syntax
Inheritance syntax
open class Parent(val x: Int) {
open fun f() { }
}
class Child(x: Int) : Parent(x) {
override fun f() { super.f() }
}
abstract class Base { abstract fun g(): Int }
interface Ability { fun act(); fun extra() = "default" }
class Thing : Base(), Ability {
override fun g() = 1
override fun act() { }
}Kotlin Inheritance Summary
- Kotlin classes and methods are final by default;
openallows inheritance and overriding. class Child(...) : Parent(...)inherits;overridereplaces an open member;supercalls the parent's version.- Abstract classes can't be instantiated and declare abstract members subclasses must implement.
- Interfaces define abilities, can have default methods, and a class can implement many.
- Polymorphism lets one piece of code work with many subclasses;
ischecks and smart casts.
Kotlin Inheritance Examples
Each example shows the code, the output it produces and what each part does.
Accounts with shared and special behaviour
open class Account(val owner: String, var balance: Double) {
open fun monthEnd() { }
fun summary() = "$owner: £${"%.2f".format(balance)}"
}
class Savings(owner: String, balance: Double, private val rate: Double) : Account(owner, balance) {
override fun monthEnd() {
balance += balance * rate
}
}
class Current(owner: String, balance: Double) : Account(owner, balance) {
override fun monthEnd() {
super.monthEnd()
balance -= 2.0
}
}
fun main() {
val accounts = listOf(Savings("Sam", 1000.0, 0.01), Current("Lea", 500.0))
accounts.forEach { it.monthEnd() }
accounts.forEach { println(it.summary()) }
}Sam: £1010.00
Lea: £498.00Line-by-line explanation
- Lines 1 to 2Account is open, and monthEnd is open so each kind of account can customise it.
- Line 6Savings passes owner and balance on to the parent constructor.
- Lines 13 to 16Current calls the parent's version first, then charges a fee.
- Line 21The same call runs a different monthEnd for each object: polymorphism.
An abstract class for shapes
import kotlin.math.PI
abstract class Shape {
abstract val name: String
abstract fun area(): Double
fun describe() = "$name with area ${"%.2f".format(area())}"
}
class Circle(private val r: Double) : Shape() {
override val name = "Circle"
override fun area() = PI * r * r
}
class Rectangle(private val w: Double, private val h: Double) : Shape() {
override val name = "Rectangle"
override fun area() = w * h
}
fun main() {
val shapes = listOf(Circle(1.0), Rectangle(3.0, 4.0), Circle(2.0))
shapes.forEach { println(it.describe()) }
println("Total: ${"%.2f".format(shapes.sumOf { it.area() })}")
}Circle with area 3.14
Rectangle with area 12.00
Circle with area 12.57
Total: 27.71Line-by-line explanation
- Line 3Shape is abstract, so Shape() on its own would be a compile error.
- Lines 4 to 5Every subclass must provide a name and an area.
- Line 6Shared code uses the abstract members without knowing the exact shape.
- Line 22sumOf works on the parent type, adding every shape's area.
Interfaces for payments
interface Payment {
val label: String
fun charge(amount: Double): Boolean
fun receipt(amount: Double) = "$label payment of £$amount"
}
class Card : Payment {
override val label = "Card"
override fun charge(amount: Double) = amount <= 500
}
class Voucher(private var balance: Double) : Payment {
override val label = "Voucher"
override fun charge(amount: Double): Boolean {
if (amount > balance) return false
balance -= amount
return true
}
}
fun checkout(payment: Payment, amount: Double) {
val result = if (payment.charge(amount)) "approved" else "declined"
println("${payment.receipt(amount)}: $result")
}
fun main() {
checkout(Card(), 89.99)
checkout(Voucher(25.0), 40.0)
}Card payment of £89.99: approved
Voucher payment of £40.0: declinedLine-by-line explanation
- Line 4A default implementation shared by every payment type.
- Line 7A class implements an interface with a colon and no brackets, because interfaces have no constructor.
- Line 21checkout accepts any Payment, so new payment types need no changes here.
Common Kotlin Inheritance Mistakes
Inheriting from a final class
class Animal(val name: String)
class Dog(name: String) : Animal(name) // error: This type is final, so it cannot be inherited fromopen class Animal(val name: String)
class Dog(name: String) : Animal(name)Forgetting override
class Dog : Animal("Rex") {
fun sound() = "Woof" // error: 'sound' hides member of supertype and needs 'override'
}class Dog : Animal("Rex") {
override fun sound() = "Woof"
}Brackets after an interface name
class Card : Payment() { } // error: interfaces have no constructorclass Card : Payment { }Kotlin Inheritance in Real Projects
Inheritance and interfaces in practice
Android screens extend framework classes such as Activity and ViewModel, while app code is usually organised around interfaces like UserRepository, with one implementation that talks to the network and another fake one for tests. This "program to an interface" approach is what lets large teams swap pieces without breaking everything around them.
Kotlin Inheritance FAQ
What is the difference between an interface and an abstract class in Kotlin?
An abstract class can hold state, such as properties with stored values and constructor parameters, and a class can extend only one. An interface can't store state, but a class can implement as many as it likes. Use an interface to describe an ability, and an abstract class when subclasses share real data and code.
Why are Kotlin classes final by default?
So that a class can only be extended when its author has planned for it. Inheriting from a class that wasn't designed for it can break in subtle ways when the parent changes. Add open to the class, and to each member you want subclasses to override.
How do I stop a subclass from overriding a method again?
An override is itself open, so grandchildren can override it too. Write final override fun sound() = "Tweet" to stop the chain there. Any further subclass that tries to override sound gets a compile error saying the member is final.
What happens if a class implements two interfaces with the same method?
If both interfaces provide a default implementation of the same function, the compiler makes you override it in the class. Inside your override, you can call either version with super<A>.hello() or super<B>.hello(), or combine both.
Can I override a property in Kotlin?
Yes, if the parent marks it open or abstract. Write override val legs = 2 in the subclass, or override it in the constructor with override val. A val can be overridden with a var, but not the other way round.
Kotlin Inheritance Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the class so other classes can inherit from it.
class Vehicle(val wheels: Int)
How many classes can a Kotlin class extend?
What does this print?
open class A { open fun hi() = "A" }
class B : A() { override fun hi() = "B" + super.hi() }
println(B().hi())
What must a subclass of an abstract class do?
Why does Kotlin make classes final by default?
Kotlin Inheritance Quiz
5 questions to check what you learned.
Which keyword replaces a parent's method?
- replace
- super
- override
- open
Can an interface have a function with a body?
- Yes, as a default implementation
- No
- Only if it's private
- Only in abstract classes
What is polymorphism?
- Having many constructors
- Copying objects
- Using many interfaces at once
- One piece of code working with objects of different classes through a shared type, each behaving its own way
What does
super.monthEnd()call?- The subclass's own version
- The parent class's version of the method
- A static function
- Nothing
Why is
class Card : Paymentwritten without brackets after Payment?- Brackets are optional everywhere
- Card has no properties
- Payment is an interface, which has no constructor to call
- It's a data class