Skip to content
Inheritance and Interfaces
step 1/7

Reading — step 1 of 7

Learn

~3 min readClasses and Inheritance

Kotlin's inheritance and interface semantics are tightened versions of Java's — final by default, single base class, multiple interfaces, default-method support.

Open class for inheritance

Unlike Java, Kotlin classes are final (closed) by default. Mark open to allow subclassing:

open class Animal(val name: String) {
    open fun speak(): String = "generic sound"
    fun describe() = "I am $name"      // final — can't override
}

class Dog(name: String, val breed: String) : Animal(name) {
    override fun speak(): String = "woof"
}

val rex = Dog("Rex", "Husky")
rex.speak()           // woof
rex.describe()        // I am Rex

Notes:

  • open on the class allows it to be extended
  • open on a method allows it to be overridden
  • override is required when overriding (Kotlin enforces this — Java's @Override is optional, Kotlin's is mandatory)
  • : Animal(name) calls the parent's primary constructor — note the parens

abstract — must subclass

abstract class Shape {
    abstract fun area(): Double            // no body — must override
    abstract val sides: Int
    
    fun describe() = "$sides-sided shape with area $area"
}

class Circle(val radius: Double) : Shape() {
    override fun area(): Double = 3.14 * radius * radius
    override val sides: Int = 0
}

Abstract members are implicitly open. Abstract classes can't be instantiated directly.

Interfaces

Kotlin interfaces are like Java 8+ interfaces — methods can have default implementations:

interface Greeter {
    val name: String
    fun greet(): String = "Hi, $name"      // default implementation
}

interface Loggable {
    fun log() = println("[$this] logged")
}

class User(override val name: String) : Greeter, Loggable {
    // greet() and log() are inherited
}

User("Ada").greet()         // "Hi, Ada"
User("Ada").log()           // "[...] logged"

A class can implement many interfaces. If two interfaces have conflicting default methods, the compiler errors and forces you to disambiguate:

interface A { fun foo() = "A" }
interface B { fun foo() = "B" }

class C : A, B {
    override fun foo() = super<A>.foo()    // pick A's
}

super<Type>.method qualifies which parent's method to call.

Property overriding

Properties from a superclass can be overridden:

open class Vehicle {
    open val wheels: Int = 0
}

class Car : Vehicle() {
    override val wheels: Int = 4
}

A val can be overridden as val, or as var (adding mutability). A var cannot be narrowed to val (would lose write access).

abstract vs interface — when to use which

  • Interface — pure capability/contract, multiple inheritance allowed, no state (no init blocks, no class-managed properties beyond abstract ones)
  • abstract class — partial implementation with state, single base, lifecycle hooks

Kotlin lets you mix interfaces freely; abstract classes are for genuine "is a kind of" hierarchies.

Visibility on overrides

An override can WIDEN visibility but not narrow it:

open class Base {
    protected open fun helper() {}
}

class Sub : Base() {
    public override fun helper() {}     // OK — protected → public
}

Common mistakes

  • Forgetting open — class/method is final by default; subclass and override fail.
  • Forgetting override — Kotlin requires it. Java made it optional.
  • Confusing class Dog : Animal with class Dog : Animal() — without parens, you're declaring an interface implementation; with parens, calling the parent's constructor.
  • Diamond inheritance ambiguity — interface conflicts force super<Type>.method disambiguation. The compiler tells you exactly what to do.
  • Trying to add state to an interface — interfaces can declare abstract properties but not store values. Use abstract class for stateful base.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…