Skip to content
Property Delegation
step 1/7

Reading — step 1 of 7

Learn

~2 min readGenerics, Delegation, Extensions

Property delegation lets a property defer its get/set to another object. Kotlin in Action treats this as one of Kotlin's signature features — it powers lazy, Delegates.observable, ViewModels, dependency injection, and more.

The by keyword

class Example {
    var name: String by Delegate()
}

Reading name calls Delegate.getValue(...). Writing calls Delegate.setValue(...). The by keyword binds the property to a delegate.

Built-in: lazy

The most-used delegate in real Kotlin code:

val expensive: String by lazy {
    println("computing...")
    "computed result"
}

fun main() {
    println("before access")
    println(expensive)        // "computing..." then "computed result"
    println(expensive)        // just "computed result" — cached
}

First access runs the lambda and caches the result. Thread-safe by default. Variants: LazyThreadSafetyMode.NONE (faster, single-threaded), LazyThreadSafetyMode.PUBLICATION (allows multiple inits, first wins).

Built-in: observable

Notify on changes:

import kotlin.properties.Delegates

class Settings {
    var theme: String by Delegates.observable("light") { prop, old, new ->
        println("${prop.name}: $old -> $new")
    }
}

fun main() {
    val s = Settings()
    s.theme = "dark"            // theme: light -> dark
    s.theme = "sepia"            // theme: dark -> sepia
}

Delegates.vetoable is similar but the lambda returns Boolean — false rejects the change.

Built-in: notNull

class User {
    var email: String by Delegates.notNull()
}

val u = User()
// u.email           // throws — never set
u.email = "[email protected]"
u.email             // "[email protected]"

For non-null vars where you can't initialize at construction.

Map-backed properties

class Config(map: Map<String, String>) {
    val baseUrl: String by map
    val timeout: String by map
}

val cfg = Config(mapOf("baseUrl" to "https://api.example.com", "timeout" to "5000"))
println(cfg.baseUrl)

The Map extension provides getValue — Kotlin's stdlib makes it Just Work.

Custom delegates

Any class with the right operators can be a delegate. The signature for a read-write delegate:

import kotlin.reflect.KProperty

class UpperCaseString {
    private var value: String = ""
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return value
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        value = newValue.uppercase()
    }
}

class User {
    var name: String by UpperCaseString()
}

fun main() {
    val u = User()
    u.name = "ada"
    println(u.name)            // ADA — delegate uppercased on set
}

For read-only properties, only getValue is needed.

Real-world delegates

  • Android viewModels() — lazy-creates a ViewModel scoped to the activity
  • remember { } in Jetpack Compose — memoizes state across recompositions
  • Koin / Hilt dependency injection — val service: Service by inject()
  • Database ORMvar name: String by columns.string("name")

Common mistakes

  • Forgetting operator modifier on getValue/setValue — required.
  • Calling lazy in tight loops — first call is fine; subsequent are still slightly slower than a plain val.
  • Using lazy on var — doesn't make sense; lazy is for one-time computation.
  • Not understanding thread mode — default is SYNCHRONIZED. NONE is faster but unsafe.
  • Mixing delegate API with field-style code — once delegated, the property is just whatever the delegate decides.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…