Published on August 8, 2025
In Kotlin, delegation is a design technique where one object hands off (“delegates”) tasks or behavior to another, avoiding rigid inheritance structures. The Kotlin language provides built-in support for delegation via the by keyword.
For example, you might have a Waiter object that handles drink orders itself but “delegates” entrée preparation to a Chef object. Delegation is “a good alternative to implementation inheritance” and requires no boilerplate code. In practical terms, you write:
interface Chef {
fun prepareEntree(name: String): Entree?
}
class RealChef : Chef {
override fun prepareEntree(name: String): Entree? = // ...
}
class Waiter(private val chef: Chef) : Chef by chef
Here, Waiter implements the Chef interface but delegates all methods to the chef instance. The compiler generates forwarding methods, so calling waiter.prepareEntree("Salad") simply calls chef.prepareEntree("Salad").
A helpful analogy is the restaurant scenario: customers never talk directly to the chef; they interact with the waiter, who in turn talks to the chef on their behalf. In code, this pattern saves you from manually writing pass-through methods.
Class Delegation allows one class to satisfy an interface by delegating to another class’s instance. For instance:
interface Printer { fun print() }
class RealPrinter(val x: Int) : Printer { override fun print() { println(x) } }
class DelegatingPrinter(p: Printer) : Printer by p
fun main() {
val real = RealPrinter(42)
DelegatingPrinter(real).print() // prints "42"
}
Here DelegatingPrinter “doesn’t override anything anymore”; it automatically forwards the print() call to the contained RealPrinter object. Internally, DelegatingPrinter(p) holds p and the compiler writes print() and any other Printer methods as calls to p.
Syntax: To delegate an interface, write syntax likeclass Derived(b: Base) : Base by b. This means Derived implements Base by forwarding calls to the object b.
Multiple Delegates: Kotlin even lets you delegate multiple interfaces to different objects. For example, a Waiter might implement two interfaces (KitchenStaff and BarStaff) by delegating each to different helpers:
interface KitchenStaff { fun prepareEntree(name: String): Entree? } interface BarStaff { fun prepareDrink(name: String): Drink? }
class Waiter( private val chef: KitchenStaff, private val bartender: BarStaff ) : KitchenStaff by chef, BarStaff by bartender { fun acceptPayment(money: Int) = println("Paid: $$money") }
This one-liner : KitchenStaff by chef, BarStaff by bartender tells Kotlin to auto-forward all KitchenStaff calls to chef and all BarStaff calls to bartender. The result is a Waiter that can handle both roles without manual boilerplate.
Overriding Delegated Methods: You can still override specific methods in the delegating class. If you write an override method in Derived, Kotlin will use that instead of the delegate’s implementation. For example:
class Greeter(b: Printer) : Printer by b { override fun print() { println("Hello!") } }
Here, Greeter.print() will always print "Hello!" even though it has a delegate. In general, Kotlin uses your overrides over the delegate’s version. However, note a subtle point: if the delegate object calls its own method internally, it won’t see your override. For instance, if RealPrinter.print() calls another method on this, it will still use its own version of that method, not the override from DelegatingPrinter.
The main benefit of class delegation is composition over inheritance. By wiring objects together, you avoid deep inheritance hierarchies while still reusing behavior. In Android development, a practical use is to create an object that implements an interface (like SharedPreferences) and delegates all calls to a concrete instance. For example, one popular technique is to define:
object AppPrefs : SharedPreferences by PreferenceManager.getDefaultSharedPreferences(App.context)
This single line means AppPrefs will automatically forward all SharedPreferences method calls to the real shared preferences instance. The object now does not override anything anymore. Instead it delegates all the SharedPreferences functions to an instance. Then you can add extension properties on AppPrefs for each preference key, yielding very clean code.
Besides class delegation, Kotlin offers property delegation. This means the getter (and setter) of a property is handled by a separate delegate object, which provides getValue and (for var) setValue methods. The syntax is:
class MyClass {
var prop: Type by delegateExpression
}
Here, delegateExpression is any object that defines the operator functions. When you read prop, Kotlin calls delegate.getValue(...). When you write prop = x, it calls delegate.setValue(...). The official docs explain: “The syntax is val/var : by . The expression after by is a delegate, because the getter/setter will be delegated to its getValue()/setValue() methods”. In other words, you can encapsulate custom logic for a property in a separate class.
For example, you might want to log every access to a property. You could write a delegate class like this (adapted from a Kotlin blog tutorial):
import kotlin.reflect.KProperty
class LoggingDelegate<T>(private var value: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("Getting ${property.name}: $value")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
println("Setting ${property.name}: $newValue (was $value)")
value = newValue
}
}
class User {
var name: String by LoggingDelegate("Unknown")
}
fun main() {
val u = User()
u.name = "Alice" // prints "Setting name: Alice (was Unknown)"
println(u.name) // prints "Getting name: Alice"
}
Here, LoggingDelegate has getValue and setValue methods. Because of var name: String by LoggingDelegate("Unknown"), any read or write to name is routed through that delegate’s methods. This pattern “reduces boilerplate for repetitive tasks” like custom getters/setters. Kotlin delegates simplify and reuse code by allowing one object to handle certain tasks on behalf of another object.
Kotlin includes several standard delegates in the stdlib. Some of the most common ones are:
lazy: Defers initialization until first access. You declare a val as by lazy { … }. The lambda inside runs the first time the property is accessed, and the result is cached for future reads. For example:
val greeting: String by lazy { println("Computing greeting") "Hello, World!" }
fun main() { println(greeting) // prints "Computing greeting" then "Hello, World!" println(greeting) // just prints "Hello, World!" (no recompute) }
By default, lazy is thread-safe (synchronized), but you can adjust its thread-safety mode if needed.
observable: Lets you run code whenever a var is changed. Using Delegates.observable(initialValue) { prop, old, new -> ... }, you provide a lambda that is called after each assignment. For example:
var score: Int by Delegates.observable(0) { prop, old, new -> println("${prop.name} changed from $old to $new") } score = 10 // prints "score changed from 0 to 10" score = 15 // prints "score changed from 10 to 15"
If you want to veto a change (i.e. conditionally reject it), use Delegates.vetoable instead. Its handler runs before assignment.
Map delegation: You can back properties by a map (useful in dynamic scenarios like JSON parsing). For example, if userData is a Map<String, Any?> with keys "name" and "age", you can write:
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } // ... val user = User(mapOf("name" to "John", "age" to 25)) println(user.name) // "John" println(user.age) // 25
Here name and age properties automatically read from the map using their property names as keys. (For var properties, use a MutableMap instead.)
Property references (::): One property can delegate to another property using the :: syntax. For example:
class C(val member: Int) { var alias: Int by this::member }
In this case, alias is effectively an alias for member.
Lateinit/lazy for UI binding: In Android, it’s common to delegate view bindings or findViewById calls to lazy delegates. For instance, in an Activity you might write:
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
This makes sure the binding is only inflated when first used. (One caution: in Fragments, view delegates should clear references in onDestroyView to avoid leaks, but the principle of lazy initialization applies.)
All delegate classes must follow certain signatures: for a val property, the delegate needs operator fun getValue(thisRef, property); for a var, it also needs operator fun setValue(thisRef, property, value). These take the owning object (or context) and a KProperty descriptor. The return type of getValue must match the property’s type.
Delegation shines in Android development by reducing boilerplate. A few notable use-cases:
SharedPreferences delegation: Instead of calling prefs.getString("key", "") everywhere, developers create a delegated wrapper. For example:
object AppSettings : SharedPreferences by PreferenceManager.getDefaultSharedPreferences(App.instance)
Then extend it:
var SharedPreferences.theme: String?
get() = getString("theme", "light")
set(v) = edit().putString("theme", v).apply()
This approach keeps all preferences in one place. Using Kotlin delegation makes the preferences setup “neat and clean” since everything is located in a single file, which probably never has to be edited again.
Fragment arguments or state: A custom delegate can simplify passing data to fragments. For example, a delegate can read/write values from the fragment’s arguments bundle under the hood. Similarly, Android KTX provides by viewModels() and by activityViewModels() for ViewModel instantiation via delegation, handling the ViewModelProvider logic for you.
View binding and findViewById: As mentioned, by lazy is a common pattern to initialize views or view binding objects only when needed, avoiding premature inflation. For example:
private val myTextView: TextView by lazy { findViewById(R.id.my_text_view) }
or using View Binding:
private val binding: MyFragmentBinding by lazy { MyFragmentBinding.inflate(layoutInflater) }
This makes UI code more concise and readable.
Each of these Android examples leverages Kotlin’s delegation to make code cleaner and more modular. Whether it’s delegating SharedPreferences, fragment arguments, or lazy-loading views, the pattern reduces repetitive code and centralizes logic.
Delegation Pattern: Kotlin’s by keyword makes the delegation pattern easy to use. Class delegation is basically inheritance by composition: you implement an interface by holding an instance and forwarding calls.
Property Delegation: With val/var x: Type by delegate, you can encapsulate getter/setter logic in a separate object. This is great for cross-cutting concerns (lazy init, observable props, map-backed data, etc.).
Standard Delegates: The standard library gives you lazy, observable, vetoable, map delegates, and more, so you can avoid boilerplate for common patterns.
Android Usage: In Android development, delegates are used for ViewModels (by viewModels()), shared preferences, fragment arguments, and view bindings, streamlining code and avoiding manual boilerplate.
Override Behavior: Remember that if you override a delegated method, your override will be used. Also note that the delegate object itself won’t see your override if it calls its own methods.
Key Insight: Delegation adheres to the “Favor composition over inheritance” principle (Gang of Four).
The “by” Keyword:
interface Engine {
fun start()
fun stop()
}
class ElectricEngine : Engine {
override fun start() = println("Engine ON")
override fun stop() = println("Engine OFF")
}
// Delegate ALL Engine methods to ElectricEngine
class Car(private val engine: Engine) : Engine by engine {
fun drive() {
start()
println("Driving...")
}
}
// Usage
val tesla = Car(ElectricEngine())
tesla.drive() // Output: Engine ON → Driving...
class LoudCar(engine: Engine) : Engine by engine {
override fun start() {
println("VROOM!")
engine.start() // Explicit delegation
}
}
observable() & vetoable(): React to Changes
var score: Int by Delegates.observable(0) { _, old, new ->
println("$old → $new")
}
score = 10 // Prints "0 → 10"
var positiveNumber: Int by Delegates.vetoable(0) { _, _, new ->
new >= 0 // Reject negative values
}
positiveNumber = -5 // Rejected (value remains 0)
Delegation is a powerful tool in Kotlin’s toolbox. By understanding and applying it, you can write cleaner, more modular code for both plain Kotlin projects and Android apps alike. For more details, see the Kotlin official documentation on delegation.