skip to content

When you override a member in a class that uses `by` delegation, do the OTHER auto-forwarded members start calling your override or the delegate's own implementation? Explain the trap.

level: middleimportance: must knowfreq 55%

answer

  1. delegate has no back-reference to wrapper
  2. self-calls stay inside the delegate
  3. no virtual dispatch into the wrapper
  4. override affects only direct wrapper calls
  5. must override every entry point

basics

~20 s

Other forwarded methods still call the delegate's own versions, not your override. The delegate doesn't know about your wrapper, so any internal calls it makes go to itself. Your override only affects calls made directly on the wrapper.

solid answer

~40 s

Delegation forwards to the delegate object, which has no reference back to your wrapper — there is no virtual dispatch into the subclass like with inheritance. If you override `add` in `class CountingList(l: MutableList<Int>) : MutableList<Int> by l`, and call `wrapper.addAll(...)`, the compiler-generated `addAll` forwards to `l.addAll(...)`. Inside, `l` calls *its own* `add`, never your `CountingList.add`. So your counter under-counts. This is the opposite trap from inheritance, where the base's `addAll` would dispatch to the subclass `add`. The fix is to override `addAll` too (and route through your own `add`), or wrap at a layer where the delegate doesn't make internal self-calls. Understanding that delegation breaks the `this`-polymorphism chain is the core insight.

code

kotlin · 9 lines
kotlin
class CountingList(private val inner: MutableList<Int>)
    : MutableList<Int> by inner {
    var added = 0; private set
    override fun add(e: Int): Boolean { added++; return inner.add(e) }
}
val l = CountingList(mutableListOf())
l.add(1)
l.addAll(listOf(2, 3))   // forwarded to inner, bypasses override
println(l.added)          // prints 1, not 3

go deeper

for a junior

Recognizes that overriding one method doesn't automatically affect others, even if unsure why.

for a middle

Explains the missing back-reference and predicts the under-count; knows to override addAll too.

for a senior

Contrasts delegation's lack of self-call interception with inheritance's virtual dispatch and frames the trade-off.

for a principal

Generalizes to a decorator-design rule: intercept at the narrowest stable seam, or pick interfaces without internal self-calls.

## The mechanism `class W(d: I) : I by d` makes the compiler emit forwarders like: ```kotlin override fun addAll(c: Collection<Int>) = d.addAll(c) ``` The forwarder calls a method **on `d`**, the delegate. Crucially, `d` is a separate object that has **no back-reference** to `W`. When `d.addAll` internally calls `add`, it calls `d.add` — its own method — because that's what `this` means inside `d`. ## Why this differs from inheritance With inheritance and an `open` base, `this` inside `Base.addAll` is the actual subclass instance, so `this.add` virtually dispatches to the override. Inheritance gives you *self-call interception*; delegation deliberately does **not**. ```kotlin class CountingList(private val inner: MutableList<Int>) : MutableList<Int> by inner { var added = 0; private set override fun add(e: Int): Boolean { added++; return inner.add(e) } // addAll is auto-forwarded to inner -> bypasses our add! } val l = CountingList(mutableListOf()) l.addAll(listOf(1, 2, 3)) println(l.added) // 0, NOT 3 — addAll forwarded straight to inner ``` ## The trap, stated plainly - Override affects **only** direct calls on the wrapper. - Any work the delegate does via its **own** internal calls is invisible to your override. - This is the flip side of the fragile base class problem: delegation is *robust* against base-internal call changes precisely because it does not intercept them — but that also means partial overrides can be inconsistent. ## Fixes - Override every entry point that must be intercepted (`add`, `addAll`, `set`, ...). - Or choose a delegate/interface whose methods don't self-call. - Or use a decorator that re-routes bulk ops through the single-element override yourself. ## Recall The delegate has no `this` pointing back at you, so self-calls stay inside the delegate.

  • How would inheritance behave differently here?
    If `addAll` were `open` on a base and implemented via `this.add`, the subclass override of `add` would be called by `addAll` (virtual dispatch), so the count would be correct — at the cost of fragile-base coupling.
  • How do you make CountingList correct?
    Also `override fun addAll(c) { c.forEach { add(it) } }` so the bulk op routes through your own `add`.

You hire a subcontractor and inspect deliveries at your door; their internal sub-sub-contracts never pass through your door for inspection.

saying these in an interview costs you the question

  • Claiming the delegate calls back into the wrapper's overrides
  • Saying delegation gives the same self-call polymorphism as inheritance
  • Not recognizing the under-count bug in CountingList
  • Thinking overriding one method is always enough for consistency

context