Delegation in Kotlin
Kotlin delegation can replace two common patterns: first, inheritance, when multiple classes need the same behavior from a common interface but single inheritance allows only one parent class—favoring composition over inheritance; and second, helper (utility) classes, where shared code would otherwise require manually forwarding methods from class to class.
The by keyword in Kotlin delegation allows a class to use an existing object that already implements a common interface to delegate its implemented methods or access its fields. This enables code reuse without using inheritance or relying on shared utility class code with manual forwarding.
When delegating methods, the class automatically forwards interface calls to the delegate. When delegating properties, the class can manage property access and behavior via built-in or custom delegates, providing benefits beyond simple field access, such as:
- Encapsulating property logic (e.g., lazy initialization, observation, conditional updates) without writing custom getters/setters.
- Reusing property behavior across multiple classes without repeating code.
- Exposing the delegated property as if it belongs to the delegating class, keeping the API clean.
Built-in delegates include:
lazy– Initializes a property only on its first access (deferred initialization).observable– Reacts to a property's value changing.vetoable– Conditionally accepts a new property value.
Kotlin Delegation Use Cases
- For unrelated classes – Shares behavior between classes that have no inheritance relationship
(e.g., both
CarandHousedelegating to a commonLoggerobject for logging). - For related classes – Reuses behavior without forcing it into a base class, avoiding inheritance pollution
(e.g.,
RepositoryandServicedelegating caching to a commonCacheobject).
Kotlin Delegation vs Inheritance
Inheritance
Delegation
Relationship
"IS-A" → subclass is a type of its parent (Dog IS-A Animal)
"HAS-A / USES-A" → class contains or relies on another object (Car HAS-A Engine, Repository USES-A Logger)
Best suited for
Modeling type hierarchies with shared core logic
Reusing specific behavior across both related and unrelated classes without forcing them into a hierarchy
Usage in Kotlin
open class, override for subclassingby keyword for interface and property delegation
Key Kotlin Delegation Features
by keyword, a class can delegate
interface implementation to another object. Once declared, the compiler automatically forwards
the interface method calls to that delegate (i.e., the class accesses methods from another class),
eliminating the need for manual forwarding and reducing boilerplate.
Different Ways Kotlin Uses Delegation: Code Examples
Interface Delegation with by
Kotlin enables interface delegation using the by keyword, allowing a class to delegate interface implementation to another instance, eliminating boilerplate.
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println("Log: $message")
}
class Service(logger: Logger) : Logger by logger {
fun performTask() {
log("Task performed") // Delegated call
}
}
fun main() {
val service = Service(ConsoleLogger())
service.performTask() // Output: Log: Task performed
}
Without Delegation: Manual Forwarding
Without the by keyword, each interface method must be manually forwarded. This quickly becomes verbose for larger interfaces.
class ServiceWithoutDelegation(private val logger: Logger) : Logger {
override fun log(message: String) = logger.log(message) // Manual forwarding
fun performTask() {
log("Task performed")
}
}
Property Delegation in Kotlin
Built-in Delegates
Kotlin provides standard property delegates to simplify property management:
// Lazy initialization
val lazyValue: String by lazy {
println("Computed!")
"Hello"
}
fun main() {
println(lazyValue) // Prints "Computed!" then "Hello"
println(lazyValue) // Only prints "Hello"
}
// Observable properties
import kotlin.properties.Delegates
var name: String by Delegates.observable("initial") { _, old, new ->
println("Changed from $old to $new")
}
fun main() {
name = "first" // Prints: Changed from initial to first
name = "second" // Prints: Changed from first to second
}
// Vetoable properties
import kotlin.properties.Delegates
var age: Int by Delegates.vetoable(0) { _, old, new ->
new >= 0 // Only accept non-negative values
}
fun main() {
age = 25 // Accepted
age = -5 // Ignored
}
Custom Property Delegates
Create custom delegates by implementing getValue and setValue. These encapsulate reusable property logic, keeping it decoupled from business logic.
import kotlin.reflect.KProperty
class UpperCaseDelegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value.uppercase()
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value
}
}
class Example {
var text: String by UpperCaseDelegate()
}
fun main() {
val example = Example()
example.text = "delegation"
println(example.text) // Output: DELEGATION
}