Kotlin Delegation
Delegation is a design pattern where one object forwards responsibilities to another. Instead of inheriting behavior, a class delegates to a helper object. Kotlin supports delegation natively with the by keyword — both for classes and for properties — eliminating boilerplate forwarding code.
The Problem: Boilerplate Forwarding
interface Printer {
fun print(doc: String)
fun scan(): String
}
class RealPrinter : Printer {
override fun print(doc: String) = println("Printing: $doc")
override fun scan() = "Scanned document"
}
// Without delegation — must manually forward every function:
class PrinterProxy(private val real: RealPrinter) : Printer {
override fun print(doc: String) = real.print(doc) // boilerplate
override fun scan(): String = real.scan() // boilerplate
}Class Delegation with by
// With delegation — Kotlin generates all forwarding automatically:
class LoggingPrinter(private val real: RealPrinter) : Printer by real {
// Only override what you want to customize
override fun print(doc: String) {
println("[LOG] Document sent to printer")
real.print(doc)
}
// scan() is automatically forwarded to real.scan()
}
fun main() {
val base = RealPrinter()
val logging = LoggingPrinter(base)
logging.print("Report.pdf") // logs + prints
println(logging.scan()) // forwarded directly to RealPrinter
}Output:
[LOG] Document sent to printer
Printing: Report.pdf
Scanned documentDelegation Diagram
LoggingPrinter
┌───────────────────────────────┐
│ implements Printer │
│ overrides print() ← custom │
│ scan() ─────────────────────┼──▶ RealPrinter.scan()
└───────────────────────────────┘ (forwarded by 'by')
The 'by real' clause tells Kotlin:
"For any Printer method I don't override, call real's version."
Multiple Interface Delegation
interface Logger {
fun log(msg: String)
}
interface Cache {
fun cache(key: String, value: String)
fun get(key: String): String?
}
class ConsoleLogger : Logger {
override fun log(msg: String) = println("[LOG] $msg")
}
class SimpleCache : Cache {
private val store = mutableMapOf()
override fun cache(key: String, value: String) { store[key] = value }
override fun get(key: String) = store[key]
}
// DataService delegates Logger to ConsoleLogger and Cache to SimpleCache
class DataService(
logger: Logger = ConsoleLogger(),
cache: Cache = SimpleCache()
) : Logger by logger, Cache by cache {
fun fetchUser(id: String): String {
val cached = get(id)
if (cached != null) {
log("Cache hit for $id")
return cached
}
log("Fetching user $id from server")
val user = "User_$id"
cache(id, user)
return user
}
}
fun main() {
val service = DataService()
println(service.fetchUser("101"))
println(service.fetchUser("101")) // second call is cached
} Output:
Fetching user 101 from server
User_101
Cache hit for 101
User_101Property Delegation with by
Properties can also delegate their get/set logic to another object. This is how lazy and observable work (covered in the next topic):
class Delegate {
operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): String {
return "Value from delegate for '${property.name}'"
}
operator fun setValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>, value: String) {
println("Setting '${property.name}' to '$value'")
}
}
class Example {
var myProp: String by Delegate()
}
fun main() {
val ex = Example()
println(ex.myProp) // calls getValue
ex.myProp = "Hello" // calls setValue
}Output:
Value from delegate for 'myProp'
Setting 'myProp' to 'Hello'Practical Example: Feature Flag Service
interface FeatureFlags {
fun isEnabled(feature: String): Boolean
fun enable(feature: String)
fun disable(feature: String)
}
class InMemoryFlags : FeatureFlags {
private val flags = mutableSetOf()
override fun isEnabled(feature: String) = feature in flags
override fun enable(feature: String) { flags.add(feature) }
override fun disable(feature: String) { flags.remove(feature) }
}
class AuditedFlags(inner: InMemoryFlags) : FeatureFlags by inner {
override fun enable(feature: String) {
println("[AUDIT] Feature enabled: $feature")
inner.enable(feature)
}
override fun disable(feature: String) {
println("[AUDIT] Feature disabled: $feature")
inner.disable(feature)
}
}
fun main() {
val flags = AuditedFlags(InMemoryFlags())
flags.enable("dark_mode")
flags.enable("beta_ui")
println("dark_mode: ${flags.isEnabled("dark_mode")}")
flags.disable("beta_ui")
} 