Skip to main content

Inheritance and interfaces

Kotlin classes and members are final by default. Mark them open only when extension is part of the design.

open class Animal(open val name: String) {
open fun sound() = "Unknown"
}

class Dog(override val name: String) : Animal(name) {
override fun sound() = "Woof"
}
Open in Kotlin Playground
Expected: Pixel: Woof. Runtime dispatch selects Dog.sound().

Abstract class versus interface​

interface Retryable {
fun retry()
fun canRetry(attempt: Int): Boolean = attempt < 3
}

abstract class BaseWorker(protected val logger: Logger) {
abstract fun execute()
fun logStart() = logger.log("Starting")
}
  • Interface: describes a capability; a class may implement several.
  • Abstract class: shares state, protected implementation, and one inheritance relationship.

Interface conflict​

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

class Combined : A, B {
override fun label(): String = super<A>.label() + super<B>.label()
}

Kotlin forces an explicit decision when two inherited defaults conflict.

✕ Deep inheritance for code reuse

open class BaseRepository
open class CachedRepository : BaseRepository()
class UserRepository : CachedRepository()

✓ Compose explicit collaborators

class UserRepository(
private val api: UserApi,
private val cache: UserCache,
)

Composition keeps dependencies visible and avoids inheriting behavior a subtype does not truly represent.

Interview-ready answer

Kotlin is final by default. Inheritance models a real “is-a” relationship; interfaces model capabilities; abstract classes can share state and implementation. Prefer composition for reusable collaborators because it keeps dependencies and replacement boundaries explicit.