iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Interfaces

Kotlin interfaces: methods + default impls, properties, multiple inheritance, and the patterns that keep API surfaces tidy.

Kotlin — interfaces

EXAMPLE
// ===== Basic =====
interface Greetable {
    fun greet(): String
}

class User(val name: String) : Greetable {
    override fun greet() = "Hi $name"
}

// ===== Default implementations =====
interface Greetable2 {
    val name: String
    fun greet() = "Hi $name"    // default — implementers can override
}

class Bot(override val name: String) : Greetable2   // greet() inherited

// ===== Multiple inheritance + conflict resolution =====
interface A { fun act() = println("A") }
interface B { fun act() = println("B") }

class C : A, B {
    override fun act() {
        super<A>.act()    // pick a specific parent's impl
        super<B>.act()
    }
}

// ===== Abstract properties =====
interface Repo<T> {
    val name: String
    fun find(id: Int): T?
}

class UserRepo : Repo<User> {
    override val name = "users"
    override fun find(id: Int): User? = null
}

// ===== Sealed interfaces (closed family) =====
sealed interface Result<out T> {
    data class Ok<T>(val value: T) : Result<T>
    data class Err(val msg: String) : Result<Nothing>
}

fun handle(r: Result<Int>) = when (r) {
    is Result.Ok -> r.value
    is Result.Err -> -1
}

// ===== Functional interface (SAM) =====
fun interface OnClick {
    fun onClick(view: View)
}

// SAM conversion allows a lambda:
button.setListener { v -> println("clicked $v") }

// ===== Delegation (by) =====
class CachingRepo<T>(private val inner: Repo<T>) : Repo<T> by inner {
    private val cache = mutableMapOf<Int, T>()
    override fun find(id: Int): T? = cache.getOrPut(id) { inner.find(id) ?: return null }
    override val name get() = "caching-${inner.name}"
}

// 'by inner' generates default delegating impls; override specific methods.

// ===== Companion + interface (static-like) =====
interface Factory<T> {
    fun create(): T
}

class Order(val id: String) {
    companion object : Factory<Order> {
        override fun create() = Order(java.util.UUID.randomUUID().toString())
    }
}

// Order.create()   // uses companion implementing the interface

// ===== Patterns to internalise =====
// - Interfaces for behaviour contracts; abstract class for partial state
// - Default methods to evolve interfaces without breaking implementers
// - Sealed interfaces for exhaustive 'when' over closed hierarchies
// - 'by' delegation for decorator patterns

// ===== Pitfalls =====
// - Diamond inheritance without resolving super<X>
// - Default methods that depend on other unimplemented methods -> runtime gaps
// - Sealed interfaces across files in non-same module -> compile errors
// - Big 'God' interfaces -> split by responsibility (ISP)

Why it matters

Kotlin interfaces support default methods, properties, multiple inheritance with super resolution, sealed for closed hierarchies, and by-delegation for decorators. They are the primary tool for shaping behaviour contracts. Keep them small, use sealed when the family is fixed, and lean on by for composition over inheritance.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
interface Greet {
    fun hello(): String
    fun wave() = "👋"
}
class User : Greet {
    override fun hello() = "hi"
}
Try it Yourself »

Discussion

Loading…