skip to content

What does Delegates.observable do, and how do you use it to run code every time a property changes?

level: juniorimportance: must knowfreq 55%

answer

  1. observable(initial) { prop, old, new -> }
  2. Callback runs AFTER the write
  3. Cannot reject — only reacts
  4. Lives in kotlin.properties.Delegates
  5. Fires even if new == old

basics

~20 s

Delegates.observable lets a property call a small function every time you assign it a new value. You give it a starting value and a callback that runs after each change, so you can react to updates.

solid answer

~30 s

Delegates.observable(initialValue) { property, oldValue, newValue -> ... } creates a delegate for a `var` using the `by` keyword. You pass an initial value and a lambda. After every assignment the lambda runs, receiving the KProperty metadata, the previous value, and the new value. The callback fires *after* the field is already updated, so it cannot block the change (unlike vetoable). Typical uses: logging, triggering UI refresh, or invalidating a cache when state changes. It lives in `kotlin.properties.Delegates`. Because it only reacts, it never affects whether the assignment succeeds.

code

kotlin · 10 lines
kotlin
import kotlin.properties.Delegates

var status: String by Delegates.observable("idle") { _, old, new ->
    println("status: $old -> $new")
}

fun main() {
    status = "running" // status: idle -> running
    status = "done"    // status: running -> done
}

go deeper

for a junior

Knows it runs a callback on each assignment and uses by + initial value correctly.

for a middle

Explains the after-the-write timing and the three lambda parameters, distinguishes it from vetoable.

for a senior

Notes it fires even on equal values, that it returns a ReadWriteProperty, and points to invalidation/caching use cases.

for a principal

Discusses thread-safety expectations, reentrancy if onChange reassigns the property, and when an explicit setter or Flow is a better fit than a delegate.

## What it is `Delegates.observable` is a factory in the `kotlin.properties.Delegates` object that returns a *property delegate*. A delegate is an object that the `by` keyword hands a property's get/set logic to. You attach it to a `var`, and Kotlin routes every read/write through the delegate. ## Signature and parameters ```kotlin fun <T> observable( initialValue: T, onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit ): ReadWriteProperty<Any?, T> ``` - `initialValue` — the value the property starts with. - `onChange` — a lambda invoked **after** each assignment completes. Its parameters are: - `property: KProperty<*>` — reflection metadata; `property.name` gives the property name. - `oldValue` — the value before this assignment. - `newValue` — the value just stored. ## Key timing rule The callback runs **after** the new value is already written to the backing field. So `observable` can only *react*; it cannot reject or alter the change. (To reject, use `vetoable`.) ## Example ```kotlin import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("${prop.name} changed: '$old' -> '$new'") } } fun main() { val u = User() u.name = "Alice" // prints: name changed: '<no name>' -> 'Alice' u.name = "Bob" // prints: name changed: 'Alice' -> 'Bob' } ``` ## When it does NOT fire The callback only fires on **assignment** (`=`). Reading the property does not trigger it. Assigning the *same* value still fires the callback (observable does not compare for equality before calling). ## Common uses - Logging/auditing state changes. - Marking a view 'dirty' so it re-renders. - Keeping a derived field or cache in sync.

  • Does the callback fire before or after the field is updated?
    After. The new value is already stored when onChange runs, which is why observable cannot reject a change.
  • What are the three parameters of the onChange lambda?
    The KProperty metadata, the old value, and the new value.

Like a doorbell that rings after someone has already walked in — you find out about the change, but you can't stop them entering.

saying these in an interview costs you the question

  • Saying observable can prevent/cancel an assignment (that's vetoable)
  • Claiming the callback runs on property reads
  • Thinking it only fires when the value actually differs
  • Confusing it with by lazy
  • Not knowing it requires the by keyword

context