When you write `class W(val d: I) : I by d`, when is the delegate expression evaluated, and what surprising behavior arises if you pass a property versus a fresh expression? Contrast with overriding the property.
answer
- delegate captured once at construction
- stored in synthetic field, no per-call re-eval
- property override doesn't feed delegate's own methods
- var reassignment doesn't retarget forwarding
- value capture, not live binding
basics
~20 sThe delegate is evaluated once when the object is built, and that exact value is stored and used for all forwarding. If you later change a property, the forwarders keep using the original delegate they captured, which can surprise you.
solid answer
~50 sIn `class W(d: I) : I by d`, the compiler evaluates the `by` expression once during construction and stores it in a hidden field; every auto-forwarder calls that captured field. A common surprise: if you delegate `by d` where `d` is a constructor parameter and you *also* declare an overriding `open val d` whose value differs, the forwarders still use the captured constructor-time value, not the overridden property — because delegation captured the expression result, not the property getter. The fix when you need late/overridable delegates is to delegate to an interface implemented by a property you control, or route through an explicit field. Practically: treat the delegate as immutable and set at construction. This 'capture once' rule also means delegating `by someMutableVar` snapshots the current value; reassigning the var afterward does not retarget forwarding.
code
kotlin · 12 linesinterface Base { val msg: String; fun show(): String }
class Impl(override val msg: String) : Base { override fun show() = msg }
open class W(b: Base) : Base by b {
override val msg: String = "override"
}
fun main() {
val w = W(Impl("orig"))
println(w.msg) // "override" (direct access)
println(w.show()) // "orig" (forwarded to captured delegate b)
}go deeper
Understands the delegate is set once at creation and reused.
Can state that property override doesn't change forwarded behavior and that vars are captured by value.
Explains the synthetic-field codegen and predicts the show()/msg surprise; knows how to get a dynamic delegate.
Treats delegate immutability as a design invariant and documents extension/identity contracts for wrapper APIs.
## Capture-at-construction When you write: ```kotlin interface I { fun f(): Int } class W(d: I) : I by d ``` the compiler generates roughly: ```kotlin class W(d: I) { // d is a constructor param private val $delegate: I = d // evaluated ONCE, stored fun f(): Int = $delegate.f() } ``` The `by` expression is evaluated a single time at construction and stored in a synthetic field. All forwarders read that field. There is **no** re-evaluation per call. ## Surprise: delegating to a parameter you also override as a property Consider: ```kotlin interface Base { val msg: String; fun show(): String } class Impl(override val msg: String) : Base { override fun show() = msg } open class W(b: Base) : Base by b { override val msg: String = "override" // shadows for direct access } ``` Calling `W(Impl("orig")).show()` returns **"orig"**, not "override". Why? `show()` is auto-forwarded to the captured delegate `b`, and `b.show()` reads `b.msg` ("orig"). Your `override val msg` only changes what `w.msg` returns directly; it does not feed back into the delegate's own `show()`. This is the same no-back-reference rule as the self-call trap, applied to properties. ## Snapshot semantics with vars ```kotlin var current: I = first class W : I by current // illegal at top level, but illustrative: ``` More concretely, `class W(d: I) : I by d` with `W(myVar)` captures `myVar`'s *value*; later `myVar = other` does not change `W`'s forwarding target. The delegate is a value, not a live binding. ## When you need a dynamic delegate - Delegate to an interface whose single method returns the current target, and implement forwarding yourself; or - Hold the delegate in a `var` field and write explicit forwarders (give up `by`'s autogen); or - Recreate the wrapper when the target changes. ## Contrast summary | Aspect | `by` delegation | property override | |---|---|---| | Affects forwarded methods | only via the captured delegate's own logic | no — override only changes direct access | | Evaluation time | once at construction | each get (normal property) | ## Recall The delegate is a captured value; overriding a property doesn't reach back into the delegate.
- Why does w.show() ignore the overridden msg?show() is forwarded to the captured delegate b; inside, b.show() reads b.msg, never the wrapper's overridden property — no back-reference exists.
- How would you make a wrapper whose delegate can change at runtime?Give up `by` autogen: store the target in a `var` and write explicit forwarders reading that var, or rebuild the wrapper when the target changes.
You photocopy a phone number at signup (capture); changing the number in your contacts later doesn't change the photocopy the system dials.
saying these in an interview costs you the question
- Claiming the `by` expression is re-evaluated on each call
- Believing overriding a property changes what forwarded methods see
- Thinking delegating to a var creates a live, re-targetable binding
- Confusing class delegation with property delegation (Delegates.observable etc.)