Skip to content
AgileLogics
6 min read

Delegation: lazy, observable, and by

Prerequisites: Interfaces, Constructors, init, and properties

Delegation means handing a job to another object. Kotlin supports it directly with the by keyword, for both properties and whole interfaces.

Delegated properties

import kotlin.properties.Delegates

class Settings {
    val report: String by lazy {
        println("Building report...")
        "12 sales"
    }

    var theme: String by Delegates.observable("light") { _, old, new ->
        println("Theme changed: $old -> $new")
    }
}

fun main() {
    val s = Settings()
    println("Settings created")
    println(s.report)
    println(s.report)
    s.theme = "dark"
}
Settings created
Building report...
12 sales
12 sales
Theme changed: light -> dark
  • by lazy computes the value the first time it's read, then remembers it. It's thread-safe by default. Ideal for expensive values that may never be needed.
  • Delegates.observable runs code every time the property changes. Delegates.vetoable can reject a change.

Properties can also delegate to a map, which is handy for data parsed from configuration:

class ServerConfig(values: Map<String, Any?>) {
    val host: String by values
    val port: Int by values
}

fun main() {
    val config = ServerConfig(mapOf("host" to "localhost", "port" to 8080))
    println("${config.host}:${config.port}")
}
localhost:8080

Interface delegation

class A(inner: I) : I by inner implements interface I by forwarding every call to inner — you only write the methods you want to change:

class CountingSet<T>(
    private val inner: MutableSet<T> = mutableSetOf(),
) : MutableSet<T> by inner {
    var addAttempts = 0
        private set

    override fun add(element: T): Boolean {
        addAttempts++
        return inner.add(element)
    }
}

fun main() {
    val tags = CountingSet<String>()
    tags.add("kotlin")
    tags.add("android")
    tags.add("kotlin")
    println("${tags.size} tags, ${tags.addAttempts} add attempts")
    println("android" in tags)
}
2 tags, 3 add attempts
true

CountingSet is a full MutableSet — size, contains, remove, and the rest all forward to inner — yet only add was written by hand. This is composition over inheritance with no boilerplate.

Common mistakes

  • Using lateinit where by lazy fits better. If a value can be computed from what the object already has, lazy guarantees it's always available when read.