Chapter 14: Delegation
Delegation — this is one of the most loved and most powerful features of Kotlin! ☕✨
Delegation is the feature that makes Kotlin feel like magic when you want to reuse code without writing tons of boilerplate. It’s so clean and expressive that once you start using it, you’ll wonder how you ever lived without it.
We’re going to cover two main kinds of delegation:
- Class delegation (by keyword for classes/interfaces)
- Delegated properties (by lazy, by observable, by vetoable, and custom delegates)
I’m going to explain everything very slowly and clearly, like we’re sitting together in a Bandra café — with real-life analogies, complete runnable examples, step-by-step breakdowns, tables, common mistakes with fixes, and tons of practical code you can copy-paste and run right now.
Let’s dive in!
1. Class Delegation – The “by” Keyword for Classes & Interfaces
Class delegation lets a class automatically forward all calls to an interface (or another class) to a delegate object you provide — without writing any implementation code yourself!
Real-life analogy: You want to run a restaurant. Instead of learning how to cook everything yourself, you hire a master chef (delegate). You just say: “Whatever customer asks for from the menu (interface), give it to the chef to cook.” → You become the owner (class) but delegate all cooking work to the chef.
Syntax:
|
0 1 2 3 4 5 6 |
class MyClass(delegate: SomeInterface) : SomeInterface by delegate |
Example 1 – Basic class delegation
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Interface we want to implement interface Printer { fun print(message: String) } // Concrete implementation class ConsolePrinter : Printer { override fun print(message: String) { println("Console: $message") } } // Class that delegates all Printer calls to ConsolePrinter class Document(private val printer: Printer) : Printer by printer { fun saveAndPrint(content: String) { println("Saving document...") print(content) // Calls printer.print() automatically! } } fun main() { val console = ConsolePrinter() val doc = Document(console) doc.saveAndPrint("Hello Webliance!") // No need to implement print() yourself } |
Output:
|
0 1 2 3 4 5 6 7 |
Saving document... Console: Hello Webliance! |
Key points:
- by printer → all methods of Printer are automatically forwarded to printer
- You can still add your own methods (saveAndPrint)
- You can override any delegated method if you want
Example 2 – Overriding one method
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class FancyPrinter(private val printer: Printer) : Printer by printer { override fun print(message: String) { println("✨ Fancy print: $message ✨") } } fun main() { val fancy = FancyPrinter(ConsolePrinter()) fancy.print("Kotlin is awesome!") // Uses overridden version } |
Output:
|
0 1 2 3 4 5 6 |
✨ Fancy print: Kotlin is awesome! ✨ |
2. Delegated Properties – by lazy, observable, vetoable & Custom Delegates
Delegated properties let you delegate the getter/setter logic of a property to another object — no need to write custom getters/setters manually.
Kotlin provides several built-in delegates — the most famous ones are:
| Delegate Type | Syntax | When to use | Key Feature |
|---|---|---|---|
| lazy | by lazy { … } | Initialize only when first accessed | Thread-safe, computed once |
| observable | by observable(initial, handler) | Get notified when value changes | React to property changes |
| vetoable | by vetoable(initial, handler) | Control whether a new value is accepted | Validate before setting |
| Custom delegate | by MyDelegate() | Create your own logic | Full control |
A. by lazy – The Most Popular One
Lazy initialization → value is computed only once when first accessed.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class DatabaseConnection { val connection: String by lazy { println("Connecting to database... (this runs only once)") "Connected to MySQL on localhost" } } fun main() { val db = DatabaseConnection() println("Before accessing connection") println(db.connection) // Triggers lazy init println(db.connection) // Already initialized – no recompute } |
Output:
|
0 1 2 3 4 5 6 7 8 9 |
Before accessing connection Connecting to database... (this runs only once) Connected to MySQL on localhost Connected to MySQL on localhost |
Very common use cases:
- Expensive initialization (database connection, heavy object)
- Thread-safe by default (synchronized)
B. by observable – React to Changes
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("Guest") { property, oldValue, newValue -> println("Property '${property.name}' changed from '$oldValue' to '$newValue'") } } fun main() { val user = User() user.name = "Amit" // Triggers observer user.name = "Priya" // Triggers again } |
Output:
|
0 1 2 3 4 5 6 7 |
Property 'name' changed from 'Guest' to 'Amit' Property 'name' changed from 'Amit' to 'Priya' |
C. by vetoable – Validate Before Setting
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Temperature { var celsius: Double by Delegates.vetoable(0.0) { property, oldValue, newValue -> if (newValue < -273.15) { println("Invalid temperature! Keeping old value.") false // Veto – reject change } else { true // Accept } } } fun main() { val temp = Temperature() temp.celsius = 25.0 // OK println(temp.celsius) // 25.0 temp.celsius = -300.0 // Rejected! println(temp.celsius) // Still 25.0 } |
4. Custom Delegates – Full Power
You can create your own delegate class!
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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} from $value to $newValue") value = newValue } } class User { var username: String by LoggingDelegate("Guest") } fun main() { val user = User() println(user.username) // Getting username: Guest user.username = "Webliance" // Setting username from Guest to Webliance println(user.username) // Getting username: Webliance } |
Quick Recap Table (Your Cheat Sheet)
| Feature | Kotlin Way (Best Practice) | Key Benefit |
|---|---|---|
| Class delegation | class MyList : List by delegate | Reuse implementation without boilerplate |
| Delegated properties – lazy | val heavy: Expensive by lazy { … } | Lazy init, computed once, thread-safe |
| Delegated properties – observable | var name by Delegates.observable(…) { … } | React to changes |
| Delegated properties – vetoable | var temp by Delegates.vetoable(…) { … } | Validate before setting |
| Custom delegate | Implement getValue & setValue | Full control over property behavior |
Common Newbie Mistakes & Fixes
| Mistake | Problem | Fix |
|---|---|---|
| Forgetting by keyword | Compile error | Always write by delegate or by lazy {} |
| Using val with observable/vetoable | Compile error (they need setter) | Use var |
| Not using override in delegated methods | Compile error | override is still required |
| Creating heavy object in lazy without thread-safety | Race conditions | lazy is thread-safe by default |
| Forgetting to import Delegates | Cannot find observable, vetoable | import kotlin.properties.Delegates |
Homework for You (Let’s Make It Fun!)
- Basic Create a data class User(val id: Int, val name: String) → use copy() to create a version with changed name.
- Medium Create a class SmartList<T> : MutableList<T> by ArrayList<T>() → add a method fun printSize().
- Advanced Create a property var temperature: Double by Delegates.vetoable(0.0) that rejects values below -273.15.
- Fun Create object Settings with lazy-loaded val apiKey: String.
- Challenge Create a custom delegate that logs every get/set operation for a property.
You’ve just mastered Kotlin’s delegation superpowers — now your code can reuse logic without boilerplate and react to changes beautifully!
