0
0
Kotlinprogramming~15 mins

Custom delegated properties in Kotlin - Deep Dive

Choose your learning style9 modes available
Overview - Custom delegated properties
What is it?
Custom delegated properties in Kotlin let you control how a property’s value is stored and retrieved by writing your own delegate. Instead of the property holding its value directly, it asks another object to handle getting and setting the value. This allows you to add extra behavior like validation, lazy loading, or logging whenever the property is accessed or changed.
Why it matters
Without custom delegated properties, you would have to write repetitive code inside every property getter and setter to add extra behavior. Custom delegates let you write that logic once and reuse it easily, making your code cleaner and less error-prone. This helps especially in big projects where many properties share similar behavior.
Where it fits
Before learning custom delegated properties, you should understand basic Kotlin properties and how property delegation works with built-in delegates like lazy or observable. After this, you can explore advanced delegation patterns, reflection, or Kotlin’s standard library delegates for more powerful property control.
Mental Model
Core Idea
A custom delegated property is like handing over control of a property’s value to another object that decides how to get and set it.
Think of it like...
Imagine you have a personal assistant who manages your calendar. Instead of you remembering and updating every appointment, you tell your assistant to handle it. The assistant can add reminders, check conflicts, or log changes before updating your schedule. The property is like your calendar, and the delegate is your assistant.
Property access flow:

Property (user code)
   │
   ▼
Delegate object
 ┌───────────────┐
 │ getValue()    │
 │ setValue()    │
 └───────────────┘
   │
   ▼
Storage or logic

When you read or write the property, calls go to the delegate’s getValue or setValue methods, which handle the actual work.
Build-Up - 7 Steps
1
FoundationUnderstanding Kotlin property basics
🤔
Concept: Learn how Kotlin properties work with default getters and setters.
In Kotlin, a property is like a variable with built-in getter and setter methods. For example: var name: String = "Alice" Here, Kotlin automatically creates a getter to return the value and a setter to update it. You can also write custom getters and setters to add behavior.
Result
You can read and write properties like variables, but behind the scenes, methods control access.
Understanding that properties are backed by methods helps you see how delegation can intercept these calls.
2
FoundationBasic property delegation syntax
🤔
Concept: Learn how to use Kotlin’s built-in delegation syntax with 'by' keyword.
Kotlin lets you delegate property behavior to another object using the 'by' keyword: val lazyValue: String by lazy { println("Computing...") "Hello" } Here, the 'lazy' delegate controls when the value is computed and stored.
Result
Accessing lazyValue runs the delegate’s logic instead of a simple field access.
The 'by' keyword connects the property to a delegate object that handles its logic.
3
IntermediateCreating a custom delegate class
🤔
Concept: Write your own class that implements getValue and setValue operator functions.
To create a custom delegate, define a class with operator functions: class StringDelegate { private var storedValue = "" operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): String { println("Getting value of ${property.name}") return storedValue } operator fun setValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>, value: String) { println("Setting value of ${property.name} to $value") storedValue = value } } Use it like: var myProp: String by StringDelegate()
Result
Reading or writing myProp calls the delegate’s getValue or setValue with logging.
Knowing the delegate must provide getValue and setValue methods is key to customizing property behavior.
4
IntermediateUsing property metadata in delegates
🤔
Concept: Delegates receive property info to customize behavior based on property name or type.
The getValue and setValue functions receive a KProperty parameter that describes the property: operator fun getValue(thisRef: Any?, property: KProperty<*>): String { println("Accessing property: ${property.name}") return storedValue } This lets you write generic delegates that behave differently depending on the property they manage.
Result
Delegates can log or validate differently for each property they control.
Using property metadata allows delegates to be reusable and context-aware.
5
IntermediateDelegates with backing storage
🤔
Concept: Store property values inside the delegate instead of the class.
Instead of storing the value in the class, the delegate keeps it: class IntDelegate { private var value = 0 operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = value operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) { if (newValue >= 0) value = newValue else println("Negative values not allowed") } } var age: Int by IntDelegate()
Result
Setting age to a negative number is blocked by the delegate’s logic.
Delegates can enforce rules or validation centrally, improving code safety.
6
AdvancedDelegates with external storage and side effects
🤔Before reading on: do you think a delegate can store values outside the class or trigger actions on set? Commit to your answer.
Concept: Delegates can store data externally or perform actions like logging or notifying observers.
Delegates don’t have to store values inside themselves. They can use external storage like maps or databases: class MapDelegate(val map: MutableMap) { operator fun getValue(thisRef: Any?, property: KProperty<*>): Any? = map[property.name] operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Any?) { println("Saving ${property.name} = $value") map[property.name] = value } } class User { val data = mutableMapOf() var name: String by MapDelegate(data) } This lets properties reflect external state and trigger side effects.
Result
Property changes update external storage and print logs automatically.
Delegates can connect properties to external systems, enabling powerful reactive or persistent behaviors.
7
ExpertAdvanced delegate internals and performance
🤔Quick: Do you think Kotlin creates a new delegate instance per property or shares one? Commit to your answer.
Concept: Understanding how Kotlin creates and uses delegate instances affects memory and performance.
Each delegated property creates its own delegate instance unless you share one explicitly. This means: - Delegates with state hold separate data per property. - Stateless delegates can be shared. Also, the compiler generates code to call getValue/setValue methods efficiently. Reflection on KProperty is only used for metadata, not for value storage. Knowing this helps optimize delegate design for speed and memory.
Result
Efficient property delegation with predictable memory use and fast access.
Understanding delegate instantiation and compiler code generation prevents common performance pitfalls in large projects.
Under the Hood
When you declare a property with 'by delegate', Kotlin generates code that replaces direct field access with calls to the delegate’s operator functions getValue and setValue. The delegate object holds any state or logic needed. The property’s owner class stores a reference to the delegate instance. At runtime, reading or writing the property calls these methods, passing the owner object and property metadata. This indirection allows the delegate to control how values are stored, validated, or transformed.
Why designed this way?
Kotlin’s delegation was designed to reduce boilerplate and increase code reuse by separating property logic from the owning class. The operator functions and 'by' syntax provide a clear, concise way to customize property behavior without complex inheritance or manual getter/setter code. This design balances flexibility with performance by generating direct calls rather than relying on slow reflection for value access.
Owner class
┌─────────────────────┐
│  property: Type     │
│  delegate: Delegate  │
└─────────┬───────────┘
          │
          ▼
Delegate object
┌─────────────────────────────┐
│ operator fun getValue(...)   │
│ operator fun setValue(...)   │
│ internal storage or logic    │
└─────────────────────────────┘

Property access flow:
Owner.property → delegate.getValue(owner, property)
Owner.property = value → delegate.setValue(owner, property, value)
Myth Busters - 4 Common Misconceptions
Quick: Do you think a delegate must always store the property value inside itself? Commit to yes or no.
Common Belief:Delegates always keep the property value inside their own fields.
Tap to reveal reality
Reality:Delegates can store values anywhere, including external maps, databases, or computed on the fly.
Why it matters:Assuming delegates must store values internally limits their use and prevents powerful patterns like dynamic or persistent properties.
Quick: Can a single delegate instance be shared by multiple properties safely? Commit to yes or no.
Common Belief:Each property must have its own delegate instance to avoid conflicts.
Tap to reveal reality
Reality:Stateless delegates can be shared safely; stateful delegates usually need separate instances per property.
Why it matters:Misunderstanding this leads to unnecessary object creation or bugs from shared mutable state.
Quick: Does Kotlin use reflection every time a delegated property is accessed? Commit to yes or no.
Common Belief:Delegated property access is slow because it uses reflection on every get or set.
Tap to reveal reality
Reality:Reflection is only used to get property metadata once; actual getValue/setValue calls are direct method calls and fast.
Why it matters:Believing delegation is slow may discourage its use, missing out on cleaner, reusable code.
Quick: Can you use custom delegates with val (read-only) properties? Commit to yes or no.
Common Belief:Delegates only work with var (read-write) properties.
Tap to reveal reality
Reality:Delegates can be used with val properties by implementing only getValue; setValue is optional.
Why it matters:Thinking delegates require var limits their use in immutable designs.
Expert Zone
1
Delegates can be combined with Kotlin’s inline classes and typealiases to create zero-overhead wrappers around properties.
2
The thisRef parameter in getValue/setValue can be used to access or modify the owning object, enabling complex interactions like bidirectional binding.
3
Delegates can implement additional interfaces or use coroutines internally to support asynchronous property loading or validation.
When NOT to use
Avoid custom delegated properties when simple getters/setters suffice or when performance is critical and the overhead of delegation is unacceptable. For very simple properties, direct field access is clearer and faster. Also, if the property logic is tightly coupled to the class state, traditional methods may be easier to maintain.
Production Patterns
In production, custom delegates are used for caching expensive computations, validating input consistently, synchronizing property changes with UI, persisting settings transparently, and implementing observable properties that notify listeners on change.
Connections
Proxy design pattern
Custom delegated properties implement a form of the proxy pattern by controlling access to a property’s value.
Understanding proxies helps grasp how delegates intercept and manage property access, adding behavior without changing the owner.
Reactive programming
Delegates can trigger side effects or notifications on property changes, similar to reactive streams updating subscribers.
Knowing reactive concepts clarifies how delegates enable automatic updates and data flow in UI or data layers.
Human personal assistants
Like a personal assistant managing tasks on your behalf, delegates manage property behavior for the owner object.
This cross-domain view highlights delegation as a general principle of outsourcing responsibilities to specialized agents.
Common Pitfalls
#1Storing property value in the owner class and delegate simultaneously
Wrong approach:class BadDelegate { var value: String = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) { value = newValue } } class User { var name: String = "" // redundant storage var name by BadDelegate() }
Correct approach:class GoodDelegate { private var value: String = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) { value = newValue } } class User { var name: String by GoodDelegate() }
Root cause:Confusing where the property’s value should be stored leads to duplicated state and inconsistent data.
#2Sharing a mutable delegate instance across multiple properties with state
Wrong approach:val sharedDelegate = IntDelegate() class User { var age: Int by sharedDelegate var score: Int by sharedDelegate }
Correct approach:class User { var age: Int by IntDelegate() var score: Int by IntDelegate() }
Root cause:Not realizing that stateful delegates need separate instances causes properties to overwrite each other’s values.
#3Forgetting to implement setValue for var properties
Wrong approach:class ReadOnlyDelegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "fixed" } var name: String by ReadOnlyDelegate() // var but no setValue
Correct approach:class ReadWriteDelegate { private var value = "" operator fun getValue(thisRef: Any?, property: KProperty<*>): String = value operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) { value = newValue } } var name: String by ReadWriteDelegate()
Root cause:Delegates for mutable properties must implement both getValue and setValue; missing setValue causes compile errors.
Key Takeaways
Custom delegated properties let you control how property values are stored and accessed by writing your own delegate classes.
Delegates use operator functions getValue and setValue to intercept property reads and writes, allowing added behavior like validation or logging.
The 'by' keyword connects a property to its delegate, making delegation syntax simple and expressive.
Delegates can store values internally, externally, or compute them dynamically, offering great flexibility.
Understanding delegate instantiation and property metadata is key to writing efficient and reusable custom delegates.