skip to content

Distinguish forwarding — an object holding another and calling the same method on it — from true delegation as the term is used in prototype-based languages. Name languages that actually provide the latter, and explain what the difference changes for the wrapped object's own self-calls.

level: seniorimportance: should knowfreq 40%

answer

  1. test: whose self runs the callee's self-call
  2. JS/Self: prototype method runs with receiver's this
  3. Kotlin `by`, Go embedding: delegate keeps its own self
  4. Ruby has both: include vs Forwardable
  5. delegation buys open recursion, costs deopt and exposure

basics

~20 s

Forwarding hands the call to another object whose self is itself. True delegation re-binds self to the original receiver, so the delegate's self-calls land back on the outer object. JavaScript prototypes, Self and Ruby's included modules delegate; Kotlin's by and Go embedding only forward.

solid answer

~60 s

The test is one line inside the callee: when the inner method calls another method on itself, whose implementation runs? - **JavaScript / Self** — a method found on the prototype runs with `this` bound to the *receiver*, so `this.name()` inside a prototype method reaches an override placed on the child object. That is Lieberman-style delegation: the behaviour lives on one object, the state and the self stay on the other, and the link is mutable per object. - **Kotlin `by` / Go embedding** — the generated stub calls `delegate.m()`; inside the delegate `this`/the receiver is the delegate, so an override on the wrapper is simply never consulted. Go states this outright: embedding is not subclassing and there is no method overriding. - **Ruby offers both, which makes the contrast sharp**: `include`/`prepend` put a module into the ancestor chain with `self` still the object (delegation-flavoured), while `Forwardable` generates `@inner.m` calls (forwarding). Delegation buys open recursion for free and costs engine deoptimization plus a delegate that must be written to be extended.

code

javascript · 9 lines
javascript
const base = {
  name() { return "base"; },
  greet() { return "hi " + this.name(); }  // self-call
};

const child = Object.create(base);
child.name = () => "child";

child.greet(); // "hi child"  - greet came from base, this is child

go deeper

for a junior

Be able to say that forwarding calls another object which keeps its own identity, and give one example of a language feature that does it.

for a middle

State the self-rebinding test and correctly classify at least Kotlin's by, Go embedding and JavaScript prototypes.

for a senior

Explain the consequences — open recursion, per-object extension, identity, engine cost — and use Ruby's include-versus-Forwardable as the demonstration.

for a principal

Frame it as an extension-surface decision: delegation turns every self-call in the delegate into a public hook you must support forever, while forwarding keeps the contract narrow at the price of hand-written cooperation.

## The distinction in one sentence Both shapes look identical from outside: you call `outer.m()` and something inside answers. The difference is *whose self* the inner method runs with. - **Forwarding**: `outer.m()` executes `inner.m()`. Inside that body, self is `inner`. Any self-call it makes stays inside `inner`. - **True delegation**: `outer.m()` finds the body on `inner` but executes it with self still bound to `outer`. Any self-call it makes is re-resolved starting from `outer` — so an override on `outer` wins. The term comes from prototype-based work in the 1980s (Lieberman's delegation paper, the Self language), where it names precisely this self re-binding. Modern languages have borrowed the *word* for features that do not do it, which is the source of most confusion in interviews. ## Languages that really delegate **JavaScript.** Every object has an internal prototype link. When a property is not found on the object it is looked up along the prototype chain, but the function is then invoked with `this` bound to the original receiver, not to the prototype that supplied it. So a method defined once on a shared prototype and calling `this.something()` will reach a `something` placed on any individual child object. The link is per object and mutable at run time. **Self.** The language the concept was designed for: there are no classes, objects have parent slots, and messages not understood are delegated to the parent with the receiver unchanged. **Ruby's module inclusion.** `include M` splices `M` into the object's ancestor chain. Methods from `M` run with `self` equal to the object, so `M`'s methods can call methods defined in the class, and vice versa — that is self-preserving lookup, not forwarding. `prepend M` inserts the module *before* the class, so `M` can wrap the class's own method and call `super`. Ruby also ships `Forwardable`, which is pure forwarding — one language, both mechanisms, and the difference is observable in the same program. ## Languages whose 'delegation' is forwarding **Kotlin's `by`** generates stubs of the form `override fun m() = delegate.m()`. Inside `delegate.m()`, `this` is the delegate. Override `name()` on the wrapper and the delegate's `greet()` still calls the delegate's `name()`. **Go's embedding** promotes methods, but the promoted method's receiver is the embedded value. The Go FAQ is explicit that embedding is not subclassing and that there is no method overriding: an embedded type can never reach out to the embedding type. **Objective-C's message forwarding** (`forwardInvocation:`) hands the whole invocation to another object, so the target's self is the target. Only an `NSProxy`-based design that re-targets and re-implements the introspection methods can pretend otherwise. ## Why the difference matters 1. **Open recursion.** Delegation gives you the inheritance property — a shared method can be specialised by overriding what it calls — without a class hierarchy. Forwarding gives you none of it, which is exactly why composition is safe: nothing the inner object does can be hijacked by whoever wrapped it. 2. **Extension without editing.** With delegation you can attach behaviour to one live object (JavaScript per-object prototypes, Ruby `obj.extend(M)`) and its existing methods immediately cooperate. With forwarding, cooperation must be designed in as an explicit callback or interface parameter. 3. **Identity.** Delegation keeps one self, so callbacks the method registers, identity comparisons and type tests all see the outer object. Forwarding produces two selves, which is the split-identity problem wrappers are known for. 4. **Cost.** Real delegation is dynamic lookup: JavaScript engines treat a mutated prototype as a shape change and can drop the object to a slow representation, and MDN warns against `Object.setPrototypeOf` in hot code. Forwarding is a direct call the compiler can inline. Delegation also makes the delegate part of your extension surface — any self-call it makes is now a hook someone can override, which is the fragile-base-class exposure that forwarding avoids by construction. ## How to answer in an interview State the self-rebinding test first, then show that you know which side of the line real features fall on. The strongest single sentence is the Ruby one: the same language gives you `include` (self preserved, cooperating) and `Forwardable` (self switched, isolated), so the distinction is not academic — it is a per-file design choice.

  • Ruby offers both `include M` and `Forwardable`. Which one gives self re-binding, and when would you deliberately pick the other?
    `include` splices the module into the ancestor chain, so its methods run with `self` as the object and cooperate with the class's own methods — that is the delegation-flavoured option. `Forwardable` generates `@inner.m` calls, so the inner object keeps its own self. You pick `Forwardable` precisely when you do not want cooperation: when the inner object is a third-party or replaceable part whose internal calls must stay inside it, or when you want to expose only three of its thirty methods.
  • If a language only forwards, how do you recover the cooperation that delegation gives for free?
    You pass the outer identity in explicitly: the inner object takes a collaborator (an interface, a function value, a listener) and calls that instead of itself, and the wrapper passes itself as that collaborator. This is the template-method hook written as a parameter rather than as an override. It is more code, but the extension points are declared rather than accidental, which is why Go recommends exactly this instead of embedding-plus-override.

saying these in an interview costs you the question

  • Claiming Kotlin's `by` or Go embedding is 'delegation' in the technical sense and expecting overrides to be visible to the delegate.
  • Saying JavaScript prototypes are 'just inheritance' and missing that the link is per object and mutable at run time.
  • Assuming forwarding is always the weaker option; forwarding's isolation is the reason wrapping a third-party object is safe.
  • Describing self re-binding as a performance optimisation rather than a semantic difference.

context