Explain the practical and performance differences between declaring a primary-constructor parameter as a property (val/var) versus a plain parameter.
answer
- val/var → lifetime field + getter
- plain param → construction-time only
- Captured plain param gets stored
- data class: all params must be val/var
- Plain param hides input from API
basics
~20 sA val/var parameter is stored as a field for the object's whole life and is readable later. A plain parameter is only available while the object is being built, so it is not kept around.
solid answer
~40 sMarking a constructor parameter `val`/`var` allocates a **backing field** that lives for the object's lifetime and exposes a getter (and setter for `var`). A plain parameter exists only during construction: it can be read by property initializers and `init` blocks but is **not** retained as a field unless something captures it. Capturing matters: if a plain parameter is referenced from a lambda, a nested/anonymous object, or used to initialize a property, the compiler keeps whatever is needed. Practically: use plain parameters for values you only transform during init (avoiding a redundant field), and `val` properties for values you must read later. For `data class`, only `val`/`var` parameters participate in `equals`/`hashCode`/`toString`/`copy` — plain parameters are not even allowed in the data-class primary constructor.
code
kotlin · 8 linesclass Order(val id: Long, rawTotal: Double) {
// rawTotal not kept as a field; only the rounded property is
val total: Double = Math.round(rawTotal * 100) / 100.0
}
val o = Order(1, 9.999)
println(o.total) // 10.0
// o.rawTotal // compile error: not a membergo deeper
Knows val/var makes a property and plain doesn't.
Knows plain params are construction-time only and val/var define the API surface.
Explains backing-field lifetime, capture-driven storage, and the data-class requirement.
Reasons about encapsulation, memory footprint per instance, and immutability defaults across a large model layer.
## Two kinds of header parameter ```kotlin class A(val kept: Int, transient: Int) { val derived = transient * 2 // transient used here, then gone } ``` - `kept` → a **property** with a backing field that survives for the object's lifetime, plus a generated getter. - `transient` → a **construction-time-only** parameter, visible to initializers and `init` blocks. ## Memory / field generation - Each `val`/`var` parameter normally produces a **backing field** on the object → real memory per instance. - A plain parameter produces **no field by itself**. If you never capture it, it lives only on the stack during the constructor call. - **Capture rule:** if a plain parameter is referenced from something that outlives the constructor — an inner/anonymous `object`, a lambda stored in a property, or a property initializer that retains it — the compiler synthesizes storage to keep it alive. So "no field" holds only when there is no such capture. ```kotlin class Counter(start: Int) { private val increment = { start + 1 } // captures start -> stored } ``` ## API surface - `val`/`var` becomes part of the **public (or chosen-visibility) API**: callers can read `obj.kept`. - A plain parameter is **invisible** outside; it cannot be read as a member. This is good for encapsulation when the raw input should not be exposed. ## data class interaction For a `data class`, **every** primary-constructor parameter **must** be `val`/`var`; plain parameters are a compile error there. Only these property parameters feed generated `equals`, `hashCode`, `toString`, and `componentN`/`copy`. ## Choosing - Need to read it later, expose it, or include it in data-class equality → `val`/`var`. - Only needed to compute something during init, and you don't want a field or public member → plain parameter. - Want mutation later → `var` (but prefer immutability; reserve `var` for genuine mutable state). ## Subtlety: shadowing vs reuse ```kotlin class B(name: String) { val name = name.trim() // property 'name' shadows the parameter after this line } ``` Here `name` (param) initializes `name` (property); after initialization only the property remains.
- Does a plain constructor parameter always avoid creating a field?Only if it isn't captured. If a lambda, inner/anonymous object, or retaining initializer references it, the compiler generates storage to keep it alive.
- Why must every parameter in a data class primary constructor be val or var?Because data classes generate equals/hashCode/toString/copy from the constructor properties, and plain parameters wouldn't be members to generate from — so the compiler forbids them.
saying these in an interview costs you the question
- Saying val and plain parameters cost the same memory regardless of capture
- Claiming plain parameters are accessible as members
- Not knowing data classes forbid plain primary-constructor params
- Always reaching for var instead of val
- Ignoring that capture can force storage for a plain param