Class delegation (class Derived(b: Base) : Base by b) implements an interface by automatically forwarding every call to a held instance, without hand-writing the forwarding code for each method — genuine composition, made nearly as concise as inheritance. Property delegation (val x by lazy { ... }) hands a property's get/set logic to a separate delegate object; lazy computes and caches a value on first access (and only then), while observable notifies a callback whenever the property's value changes, both built into the standard library and commonly used without writing a custom delegate at all.
Class delegation: composition without the boilerplate
LoggingList automatically forwards every MutableList method to innerList via by — only add() is overridden with extra behavior; everything else (size, get, remove, and the rest of MutableList's full interface) just works, forwarded automatically.
class LoggingList<T>(private val innerList: MutableList<T> = mutableListOf()) :
MutableList<T> by innerList { // forwards EVERY MutableList method to innerList automatically
override fun add(element: T): Boolean {
println("Adding: $element")
return innerList.add(element) // only this method has custom behavior
}
}
val list = LoggingList<Int>()
list.add(5) // prints "Adding: 5", then actually adds it
println(list.size) // 1 — forwarded automatically, no code written for size at allProperty delegation: lazy initialization
The expensive computation inside lazy runs only the FIRST time expensiveValue is accessed — every subsequent access reuses the cached result, with zero extra code needed to implement that caching.
val expensiveValue: String by lazy {
println("Computing...") // runs only ONCE, on first access
"the result"
}
println(expensiveValue) // prints "Computing..." then "the result"
println(expensiveValue) // prints only "the result" — cached, not recomputedProperty delegation: observable
The callback fires automatically on every reassignment, receiving both the old and new values — useful for reacting to a property's changes without manually wiring a listener pattern.
import kotlin.properties.Delegates
var name: String by Delegates.observable("initial") { _, old, new ->
println("Changed from $old to $new")
}
name = "Alice" // prints "Changed from initial to Alice"