What are the limits on what an interface default method or accessor can do regarding state, and how do teams work around the lack of interface fields?
answer
- No backing field → no `field`, no memoization in interface
- Defaults use params + other interface members only
- Push state into an abstract property the class backs
- Delegation `by` keeps state in a separate object
- Intrinsic state → use an abstract class instead
basics
~20 sDefault methods can't store or remember anything because interfaces have no fields. They can only use parameters and call other interface members. To keep state, implementers add their own property, which the interface declares as an abstract property.
solid answer
~40 sAn interface default method/accessor has **no backing field** to read or write, so it cannot hold per-instance state, cannot memoize into a field, and cannot use `field`. It may only use its parameters and delegate to other interface members (abstract or default). The standard workaround is to declare an **abstract property** in the interface that the implementing class backs with real storage; the default method then reads/writes through that property's accessors. For memoization you push the cache into the implementer (e.g., an abstract `val cache: MutableMap<...>`), or switch to an abstract class that owns the field. Delegation via `by` is another route: `class C(impl: I) : I by impl` forwards interface members to a stateful object. These patterns preserve the interface as a pure contract while letting concrete types own the state.
code
kotlin · 17 linesinterface Cache {
val store: MutableMap<String, Int> // storage owned by implementer
fun compute(key: String): Int // abstract
fun memoized(key: String): Int = // default, caches via the property
store.getOrPut(key) { compute(key) }
}
class Squares : Cache {
override val store = mutableMapOf<String, Int>()
override fun compute(key: String) = key.length * key.length
}
fun main() {
val s = Squares()
println(s.memoized("abc")) // 9, computed then cached
println(s.memoized("abc")) // 9, served from store
}go deeper
Understands a default method can't remember anything but may not know the workarounds.
Knows to add an abstract property the class backs so default methods can read/write state.
Articulates all options (abstract property, delegation by, abstract class) and chooses appropriately, knowing why field is illegal.
Designs contracts deciding which state belongs in implementers vs delegated objects vs abstract classes for testability and evolution.
## The hard limit: no fields in interfaces Interfaces have no constructor and **no backing fields**, so anything an interface default method or accessor does is **stateless from the interface's own perspective**. Concretely a default member: - Cannot declare or write an instance field. - Cannot use the `field` keyword (there is no backing field). - Cannot memoize a result into interface storage. - Can only use its **parameters** and **call other interface members** (abstract or default) — including abstract *properties* whose storage lives in the implementer. ```kotlin interface PriceList { val items: List<Double> // abstract: implementer supplies storage fun total(): Double = items.sum() // default: computes from the abstract property } ``` Here `total()` is recomputed every call; it can't cache into the interface. ## Workaround 1 — push state into an abstract property Declare the state as an abstract property the class must back. The default method then routes through it: ```kotlin interface Counter { var count: Int // class provides the backing field fun increment() { count++ } // default mutates via the property } class MemCounter : Counter { override var count = 0 } ``` The field physically lives in `MemCounter`; the interface only declares the contract. ## Workaround 2 — abstract cache property for memoization ```kotlin interface Loader { val cache: MutableMap<String, String> // storage owned by implementer fun fetch(id: String): String // abstract source fun get(id: String): String = cache.getOrPut(id) { fetch(id) } // default caches via property } ``` ## Workaround 3 — interface delegation with `by` Kotlin's class delegation forwards interface members to a stateful object, keeping the state outside the interface declaration: ```kotlin interface Repo { fun save(x: String) } class InMemoryRepo : Repo { private val store = mutableListOf<String>() // real state override fun save(x: String) { store += x } } class LoggingRepo(impl: Repo) : Repo by impl // delegates save() to impl ``` ## Workaround 4 — just use an abstract class If the state is intrinsic to the abstraction, an **abstract class** with a real `protected` field and constructor is the honest tool; interfaces are for stateless contracts. ## Key takeaway Interface defaults are computations over the contract surface; whenever you need memory, the storage must live in the implementer (abstract property), in a delegated object (`by`), or in an abstract class.
- Why can't a default method use the `field` keyword?`field` refers to a property's backing field, and interfaces have no backing fields, so there is nothing for `field` to reference.
- How does class delegation (`by`) help keep interfaces stateless?`class C(impl: I) : I by impl` forwards the interface members to a separate stateful object, so the state lives in that object, not in the interface.
A default method is a calculator with no memory button: it can compute from what you hand it, but to remember a number you need an external notepad (the implementer's property).
saying these in an interview costs you the question
- Believing a default method can store a value between calls in the interface
- Trying to use `field` inside an interface accessor
- Not knowing to push state into an abstract property
- Claiming interface delegation `by` puts state into the interface itself