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"
}
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.