When is a default argument expression evaluated — once at definition, or on each call where it is used? Show the consequences.
answer
- Per call, not once at definition
- Fresh mutableListOf() every defaulted call
- No Python mutable-default trap
- Skipped when caller passes the value
- Synthetic $default dispatcher with bitmask
basics
~10 sThe default expression runs every time the function is called without that argument. It is not computed once and cached, so each defaulted call re-evaluates it freshly.
solid answer
~40 sKotlin evaluates a default argument expression **per call**, only when the caller omits that argument. The expression is effectively inlined at the call site (via the compiler-generated synthetic dispatcher), so any side effects, function calls, or fresh-object creation happen on each defaulted invocation. For example, fun log(msg: String, time: Long = System.currentTimeMillis()) reads the clock each call, and fun newList(xs: MutableList<Int> = mutableListOf()) creates a brand-new list every defaulted call — unlike Python, which evaluates defaults once and shares a mutable default. Defaults are evaluated left to right and may reference earlier parameters. If the caller supplies the argument, the default expression is never evaluated, so an expensive default costs nothing when overridden. This per-call semantics is what makes mutable defaults safe in Kotlin.
code
kotlin · 9 linesfun collect(item: Int, into: MutableList<Int> = mutableListOf()): MutableList<Int> {
into.add(item)
return into
}
fun main() {
println(collect(1)) // [1]
println(collect(2)) // [2] fresh list each defaulted call
}go deeper
Recognizes that omitting an argument uses the default value.
Correctly states per-call evaluation and explains the fresh-object / side-effect consequences.
Contrasts with Python's shared-default trap and reasons about cost (skipped when supplied).
Explains the synthetic $default dispatcher/bitmask mechanism and implications for performance and interop.
## Per-call evaluation A Kotlin default argument is an **expression**, and that expression is evaluated **each time** the function is called *without* supplying that argument. It is not evaluated once at definition time and reused. ```kotlin fun stamp(label: String, t: Long = System.currentTimeMillis()): String = "$label@$t" stamp("a") // reads the clock now stamp("b") // reads the clock again — different value ``` Every defaulted call re-runs `System.currentTimeMillis()`. ## Why this matters for mutable defaults Because the expression runs per call, a default that **creates** an object gives a *fresh* instance each time: ```kotlin fun collect(item: Int, into: MutableList<Int> = mutableListOf()): MutableList<Int> { into.add(item) return into } println(collect(1)) // [1] println(collect(2)) // [2] — a NEW list, not [1, 2] ``` This is the opposite of Python's famous *mutable default argument* trap, where `def f(x=[])` shares **one** list across calls. In Kotlin there is no shared-default bug because the default is re-evaluated. ## Side effects and cost - A default with **side effects** (logging, counters, I/O) triggers them on every defaulted call. - An **expensive** default is *not* paid when the caller passes the argument — the expression is skipped entirely. ```kotlin fun load(key: String, cfg: Config = expensiveLoad()): Config { /* ... */ } load("x", cachedCfg) // expensiveLoad() NOT called load("x") // expensiveLoad() called ``` ## How the compiler implements it The Kotlin compiler generates a hidden **synthetic** function (the `$default` dispatcher) that takes a bitmask flag indicating which arguments were omitted; for each omitted argument it runs the default expression inline before calling the real body. That is why the expression executes at call time, in the caller's context, and can reference **earlier parameters** (evaluated left to right).
- Does Kotlin share a single mutable object across calls like Python's def f(x=[])?No. Kotlin re-evaluates the default per call, so each defaulted call gets a fresh object; there is no shared-mutable-default bug.
- If the caller passes the argument, is the default expression still evaluated?No. The expression is only evaluated when that argument is omitted, so an expensive default costs nothing when supplied.
Like a vending-machine fallback that re-brews a fresh coffee each time you don't insert your own cup — never one stale pot shared by everyone.
saying these in an interview costs you the question
- Saying the default is evaluated once at definition time
- Claiming Kotlin has Python's shared mutable-default trap
- Believing an expensive default always runs even when overridden
- Not knowing side-effecting defaults run on every defaulted call