Under exactly what conditions does the Kotlin compiler NOT generate a backing field for a property?
answer
- no field if accessors are custom AND never use `field`
- computed property = getter recomputes each read
- delegated (by) stores in the delegate, not a field
- no field => no lateinit possible
- default accessor implies a field
basics
~10 sIf the property's getter (and setter for a var) are fully custom and never use the field keyword, there's no stored value, so the compiler skips the backing field. The value is computed instead.
solid answer
~40 sThe compiler generates a backing field **only if** the property references `field` in at least one accessor, or relies on a default accessor that stores state. So a property has **no** backing field when **every** accessor is custom and **none** mentions `field`. Such a property is *computed*: a `val` with `get() = a + b` recomputes on each access; a `var` whose get/set delegate to other state stores nothing itself. Consequences: computed properties don't occupy memory, can't be `lateinit`, and re-run their getter logic each read (watch for side effects or cost). Delegated properties (`by lazy`, `by Delegates.observable`, etc.) also have **no** ordinary backing field — storage lives in the delegate object, accessed via the `getValue`/`setValue` operator functions.
code
kotlin · 9 linesclass Person(val first: String, val last: String) {
// No backing field: computed on each access
val fullName: String
get() = "$first $last"
// Has a backing field: setter uses `field`
var nickname: String = ""
set(value) { field = value.trim() }
}go deeper
Knows a property with only get() = expr computes its value rather than storing it.
States the exact rule (no field unless field is used or a default accessor is present) and its consequences.
Connects field-less properties to lateinit restrictions, init order, getter cost/side effects, and delegated-property storage.
Weighs computed vs stored as a performance/encapsulation API decision and can verify via bytecode across a codebase.
## The rule Kotlin generates a **backing field** for a property if and only if one of these holds: 1. At least one accessor uses the `field` keyword, **or** 2. The property uses the **default accessor** for at least one direction (which implicitly reads/writes storage). If you write fully custom accessors that never touch `field`, **no backing field exists**. ## Computed (field-less) properties ```kotlin class Rectangle(val width: Int, val height: Int) { val area: Int get() = width * height // no field; recomputed each read } ``` `area` stores nothing. Each read runs `width * height`. The same applies to a `var` that forwards to other storage: ```kotlin class Celsius(var c: Double) { var fahrenheit: Double get() = c * 9 / 5 + 32 set(value) { c = (value - 32) * 5 / 9 } // no field of its own } ``` ## Why it matters - **Memory**: computed properties take no per-instance storage. - **Cost & side effects**: the getter runs on every read — avoid expensive work or surprising side effects. - **No `lateinit`**: `lateinit` requires a backing field, so it can't apply to a computed property. - **Initialization**: a computed `val` has no initializer (`= ...`) and isn't part of constructor init order. ## Delegated properties also lack an ordinary field ```kotlin val config: Config by lazy { loadConfig() } ``` With `by`, storage moves into the **delegate object**; access goes through the `getValue`/`setValue` operator functions the delegate provides. There is no normal `field` here — `lazy` keeps the cached value internally. ## Quick decision table | Property form | Backing field? | |---|---| | `var x = 0` (default accessors) | yes | | `val x get() = field` | yes (uses field) | | `val x get() = a + b` | no (computed) | | `var x get()=... set(){...}` never using field | no | | `val x by lazy { ... }` | no (delegate stores it) | ## Inspecting it You can confirm by viewing the generated bytecode/decompiled Java (e.g., IntelliJ "Show Kotlin Bytecode" -> Decompile): a computed property shows only a `getX()` method and no private field.
- Why can't a computed property be `lateinit`?`lateinit` needs a backing field to hold the deferred value; a computed property has no field to initialize.
- Where is the value stored for `val x by lazy { ... }`?Inside the `Lazy` delegate instance; the property accesses it via the delegate's `getValue` operator, not an ordinary backing field.
saying these in an interview costs you the question
- Saying every property always has a backing field
- Thinking a computed getter stores its result automatically
- Believing delegated properties use a normal backing field
- Forgetting that getter side effects run on every read