skip to content

Kotlin makes delegation a language feature via the `by` keyword for both interfaces and properties. Explain class/interface delegation and property delegation, and what the compiler generates.

level: seniorimportance: should knowfreq 58%

answer

  1. by = compiler-generated forwarding (interface) or getValue/setValue routing (property)
  2. composition over inheritance; selectively override members
  3. lazy / observable / vetoable / notNull / map delegation
  4. getValue/setValue operators + optional provideDelegate
  5. gotcha: interface delegate fixed to constructor arg

basics

~20 s

by lets one object hand off work to another automatically. For interfaces, a class can implement an interface by forwarding all its methods to a held object. For properties, getting/setting a value can be routed through a helper like lazy or Delegates.observable.

solid answer

~40 s

Kotlin's `by` keyword implements the **Delegation pattern** at the language level, avoiding boilerplate forwarding code. **Class/interface delegation**: `class C(b: Base) : Base by b` makes `C` implement `Base` by auto-forwarding every interface method to `b`; the compiler generates the forwarding stubs — favoring composition over inheritance. **Property delegation**: `val x by lazy { ... }` or `var y by Delegates.observable(...)` routes the property's get/set through a delegate object that defines `operator fun getValue`/`setValue` (or `provideDelegate`). Stdlib delegates include `lazy` (thread-safe memoized init), `Delegates.observable`/`vetoable`, `Delegates.notNull`, and map-backed delegation (`val name: String by map`). The compiler stores the delegate in a synthetic field and rewrites accesses to call `getValue`/`setValue`. A subtle gotcha: interface delegation captures the delegate passed to the constructor, not an overriding property in the subclass.

code

kotlin · 9 lines
kotlin
class CachingService(
    inner: Service,
) : Service by inner {            // forward all Service methods
    private val cache by lazy { mutableMapOf<Int, String>() } // property delegate
}

var temperature: Int by Delegates.vetoable(0) { _, _, new ->
    new in -50..50               // reject out-of-range writes
}

go deeper

for a junior

Recognizes by lazy for lazy initialization and that by delegates work to another object.

for a middle

Explains interface delegation forwarding and names stdlib property delegates like observable/lazy.

for a senior

Describes the getValue/setValue operator contract, provideDelegate, and the composition-over-inheritance use of interface delegation.

for a principal

Designs decorator/proxy layers with delegation, writes custom delegates, and reasons about the constructor-fixed-delegate gotcha and generated-code cost.

## Two distinct features, one keyword `by` powers two unrelated mechanisms that both express *delegation*. ## 1. Class / interface delegation ```kotlin interface Repository { fun find(id: Int): String } class DbRepository : Repository { override fun find(id: Int) = "row$id" } class LoggingRepository( private val delegate: Repository, ) : Repository by delegate { // forwards everything to delegate override fun find(id: Int): String { // override just one method println("finding $id") return delegate.find(id) } } ``` - `: Repository by delegate` tells the compiler to **generate forwarding implementations** of every `Repository` member that call through to `delegate`. - You can selectively `override` specific members to add behavior (decorator pattern). - This realizes the *Gang of Four* maxim **"favor composition over inheritance"** without manual stubs. - **Gotcha**: the delegate is fixed to the expression in the supertype list. If you delegate to a constructor parameter, members of the delegate call back into the *delegate's* implementation, not any property you override in the outer class. ## 2. Property delegation ```kotlin val config: Config by lazy { loadConfig() } // memoized, thread-safe var volume: Int by Delegates.observable(0) { _, old, new -> println("$old -> $new") } ``` The delegate is any object providing the operator functions: ```kotlin operator fun getValue(thisRef: T?, property: KProperty<*>): V operator fun setValue(thisRef: T?, property: KProperty<*>, value: V) // var only ``` The compiler: - stores the delegate in a synthetic field (e.g. `config$delegate`), - replaces reads with `delegate.getValue(this, ::config)` and writes with `setValue(...)`, - optionally calls `provideDelegate` once at construction to create/validate the delegate. ### Stdlib delegates - `lazy { }` — computes once on first read; default mode `SYNCHRONIZED` is thread-safe (also `PUBLICATION`, `NONE`). - `Delegates.observable(initial) { prop, old, new -> }` — fires after each change. - `Delegates.vetoable(initial) { ... -> Boolean }` — can reject a change. - `Delegates.notNull<T>()` — non-null property initialized later; throws if read first. - **Map delegation**: `class User(map: Map<String, Any?>) { val name: String by map }` reads from the map by property name — handy for JSON/dynamic data. ## Why it matters `by` turns two well-known patterns — Decorator/Proxy (interface delegation) and lazy/observable accessors (property delegation) — into one-liners the compiler verifies, eliminating error-prone hand-written forwarding and getter/setter logic.

  • What two operator functions must a read-write property delegate provide?
    `operator fun getValue(thisRef, property)` and `operator fun setValue(thisRef, property, value)`; a read-only (`val`) delegate needs only `getValue`.
  • What's the gotcha when delegating an interface to a constructor parameter?
    Forwarded calls always go to that fixed delegate instance; overriding a member in the outer class does not change what the delegate's own methods call.

Interface delegation is hiring an assistant who answers every call exactly as you would, except the few you handle personally; property delegation is a smart valet that fetches the value (and remembers it) whenever asked.

saying these in an interview costs you the question

  • Confusing interface delegation with property delegation as the same mechanism
  • Thinking `by lazy` is the only property delegate
  • Not knowing `getValue`/`setValue` operator contract
  • Claiming interface delegation requires writing forwarding methods manually
  • Believing overriding in the outer class redirects the delegate's internal calls

context