In a class using interface delegation, if you both delegate to an object and explicitly override one method, which implementation runs — and does the delegate still see the override?
answer
- Override wins for external calls
- Delegate self-calls bypass your override
- No virtual dispatch across the boundary
- One-way forwarding: outer → delegate
- Inheritance would re-dispatch; delegation does not
basics
~10 sYour explicit override always runs for that method. But the delegate object never knows about your override — if it calls another of its own methods internally, it calls its own version, not yours.
solid answer
~40 sAn explicit override in the delegating class takes precedence over the compiler-generated forwarder for that specific member. However, the delegate is a **separate object** that has no reference back to your class. So if a delegate method internally calls another interface method ("self-call"), it dispatches to the delegate's own implementation — your override is bypassed. This is the classic difference from inheritance, where virtual self-calls would hit overrides. With delegation there is no virtual dispatch across the boundary: forwarders are one-way (outer → delegate). This makes delegation safer against fragile-base-class self-call surprises but also means you cannot intercept the delegate's internal calls just by overriding.
code
kotlin · 16 linesinterface Greeter {
fun name(): String
fun greet(): String
}
class Default : Greeter {
override fun name() = "World"
override fun greet() = "Hello, ${name()}" // self-call
}
class Loud(d: Greeter) : Greeter by d {
override fun name() = "KOTLIN"
}
fun main() {
val g = Loud(Default())
println(g.name()) // KOTLIN (override)
println(g.greet()) // Hello, World (Default.greet self-calls Default.name)
}go deeper
Knows an explicit override beats the forwarder for direct external calls.
Explains the self-call gap: delegate's internal calls bypass the override because there's no cross-boundary virtual dispatch.
Names it the self/open-recursion problem and contrasts cleanly with inheritance's virtual self-calls.
Reasons about when this safety property is desirable vs. limiting and how to architect collaborators to intercept internal calls.
## Two layers: outer class vs delegate With `class Wrapper(d: Api) : Api by d`, there are **two distinct objects**: the `Wrapper` and the delegate `d`. The compiler generates forwarders on `Wrapper` that call `d`. The delegate `d` holds **no reference** to `Wrapper`. ## Override precedence (external calls) If you explicitly `override` a member in `Wrapper`, that override replaces the generated forwarder for that member. So when **external code** calls `wrapper.foo()`, your override runs: ```kotlin interface Api { fun a(): String fun b(): String } class Base : Api { override fun a() = "Base.a" override fun b() = "a=${a()}, Base.b" // self-call to a() } class Wrapper(d: Api) : Api by d { override fun a() = "Wrapper.a" } fun main() { val w = Wrapper(Base()) println(w.a()) // "Wrapper.a" (override wins) println(w.b()) // "a=Base.a, Base.b" (forwarded to Base.b, whose a() is Base.a!) } ``` ## The self-call gap (key insight) `w.b()` forwards to `Base.b()`. Inside `Base.b()`, the call `a()` is a call on **`Base`'s own `this`**, so it resolves to `Base.a()`, **not** `Wrapper.a()`. The delegate cannot "see" the override because there is **no virtual dispatch across the delegation boundary** — forwarding is strictly one-directional (outer → delegate). Contrast with inheritance: if `Wrapper` *extended* `Base` and overrode `a()`, then `b()`'s self-call to `a()` would dispatch virtually to `Wrapper.a()`. Delegation deliberately does not do this. ## Consequences - **Safer**: no fragile-base-class self-call coupling; the delegate behaves consistently. - **Limitation**: you cannot intercept the delegate's *internal* calls by overriding; you'd have to wrap the delegate's dependencies instead. - The delegate value is captured at construction; `by d` stores a hidden field initialized once. ## Naming the concept This is sometimes called the **"self problem"** or lack of **open recursion** in delegation. Keywords involved: `by`, `override`, virtual dispatch.
- How could you make the delegate's internal call use your override?You can't via delegation alone. You'd inject a different collaborator into the delegate, or use inheritance/composition where you control the self-call path explicitly.
- Is this self-call behavior a bug or by design?By design. Delegation avoids open recursion across the boundary, which is exactly what prevents fragile-base-class self-call surprises.
You can give a contractor new instructions for one task, but their internal crew still follows the contractor's own playbook, not yours.
saying these in an interview costs you the question
- Claiming the delegate's self-calls dispatch to your override
- Saying delegation behaves identically to inheritance for virtual dispatch
- Thinking the override replaces the delegate's own method too
- Not realizing the delegate is a separate object with no back-reference
- Assuming `by` re-binds the delegate on every call