How do val and var differ for class properties (getters/setters), and why can a var property block a smart cast?
answer
- val property = getter only; var = getter + setter
- Smart cast needs a STABLE value
- var, open val, custom-getter val, delegated = unstable
- Fix: capture into a local val before the check
- Custom-getter val can return different values each call
basics
~20 sA val property only gets a getter; a var property gets a getter and a setter. A var (especially a public or open one) can change between a null check and its use, so the compiler refuses to smart-cast it because the value might no longer be non-null.
solid answer
~50 sAs properties, `val` generates a getter only, while `var` generates both getter and setter. A `val` property may still have a custom getter that computes a value each call (no backing field), but it cannot be reassigned externally. Smart-casting depends on the compiler proving the value can't change between the check and the use. A local `val` is provably stable, so `if (x != null) x.length` smart-casts. A mutable `var` — and especially a property — may be changed concurrently or via a custom getter that returns different values each call, so the compiler conservatively refuses the smart cast and you must use `?.`, `?: ` , `!!`, or capture it in a local `val`. Open `val` properties also block smart-casts because a subclass could override the getter. Stability categories the compiler tracks: local vals (stable), final non-custom-getter vals, vs. var/open/delegated/custom-getter (unstable).
code
kotlin · 6 linesinterface Node { val next: Node? }
fun traverse(n: Node) {
// n.next is an interface property -> not stable
val nx = n.next // capture into local val
if (nx != null) traverse(nx) // smart cast works on the local val
}go deeper
Knows val property has a getter and var adds a setter.
Can explain a basic smart cast and that a var property can break it.
Enumerates the stability rules (var, open, custom getter, delegated) and applies the local-val capture idiom.
Reasons about thread-safety implications of mutable properties and designs APIs to keep values stable/smart-castable.
## Properties: what gets generated Kotlin properties auto-generate accessors: - **`val` property** → a `get()` only. - **`var` property** → `get()` **and** `set()`. ```kotlin class User(val id: Long, var name: String) // id: getter only; name: getter + setter ``` A `val` can still have a **custom getter** that returns a computed value with no backing field: ```kotlin class Circle(val r: Double) { val area: Double get() = Math.PI * r * r // recomputed each access } ``` Note: such a custom-getter `val` is **not stable** for smart-casting because each call could in principle return a different value. ## Smart casts, briefly A **smart cast** lets the compiler treat a value as a more specific (or non-null) type after a check, without an explicit cast: ```kotlin fun len(s: String?): Int { if (s != null) return s.length // s smart-cast to String return 0 } ``` This is only sound if the value **cannot change** between the check and the use. ## Why var (and some vals) block smart casts The compiler tracks **stability**. A smart cast is allowed only for *stable* values: - **Stable:** local `val`, `private`/`internal` `val` with a default backing field (no custom getter), function parameters. - **Unstable (no smart cast):** - **`var`** — could be reassigned, including by another thread, between check and use. - **`open val`** — a subclass might override the getter to return varying values. - **`val` with a custom getter** — could return a different value each call. - **delegated properties** (`by lazy`, `by Delegates.observable`). - **mutable properties of another module/package** the compiler can't prove are stable. ```kotlin class Box { var value: String? = null } fun f(b: Box) { if (b.value != null) { // b.value.length // ERROR: smart cast impossible, b.value is a mutable property b.value?.length // fix 1: safe call val v = b.value; if (v != null) v.length // fix 2: capture in local val } } ``` ## Practical fixes - Capture the property into a **local `val`** before the check (most idiomatic). - Use the safe-call `?.` operator. - Use `?:` Elvis for a default. - `!!` asserts non-null but throws if wrong — last resort. ## Takeaway `val` vs `var` on properties isn't just about reassignment ergonomics — it directly affects what the compiler can prove, and therefore the cleanliness of your null-handling and casting code.
- Why does an open val block a smart cast even though it can't be reassigned?A subclass can override its getter to return different values on successive calls, so the compiler can't prove stability between the check and the use.
- What's the most idiomatic fix when a property won't smart-cast?Assign it to a local val first; the local is stable and smart-casts cleanly.
saying these in an interview costs you the question
- Claiming all vals always smart-cast
- Not knowing var properties block smart casts
- Reaching for !! as the default fix instead of a local val or ?.
- Thinking a val property never has a getter (it always does)
- Believing custom-getter vals are stable for smart-casting