skip to content

Explain the four `InvocationKind` values used with `callsInPlace` and what each one permits the compiler to assume about a `val` (and a `var`) assigned inside the lambda.

level: middleimportance: should knowfreq 45%

answer

  1. EXACTLY_ONCE: val assign + read after
  2. AT_LEAST_ONCE: read after yes, val assign no (use var)
  3. AT_MOST_ONCE: val assign yes, read after no
  4. UNKNOWN: only 'in place', weakest
  5. all kinds imply synchronous, non-escaping call

basics

~20 s

The kind says how often the lambda runs: exactly once, at most once (zero or one), at least once (one or more), or unknown. Only 'exactly once' lets you assign a val and use it afterward as initialized.

solid answer

~40 s

`InvocationKind` has four members. `EXACTLY_ONCE`: the lambda runs precisely one time — a `val` assigned inside is then definitely initialized and assigned-exactly-once after the call. `AT_MOST_ONCE`: zero or one time — a `val` may be assigned, but the compiler cannot guarantee initialization afterward, so reading it later still fails unless otherwise set. `AT_LEAST_ONCE`: one or more times — it is definitely initialized afterward, but you may **not** assign a `val` inside (could run twice, violating single-assignment); a `var` is fine and is definitely initialized. `UNKNOWN`: no count guarantee — only asserts the lambda is called in place (synchronously), enabling smart-cast/initialization reasoning that doesn't depend on count, but neither val assignment nor guaranteed-initialized reads. All four still imply the lambda is invoked in place, i.e., not stored or deferred.

go deeper

for a junior

Recognizes EXACTLY_ONCE as the one that 'lets you set a val'; may not know the other three precisely.

for a middle

Correctly maps all four kinds to val-assignability and post-call initialization, and knows the 'in place' commonality.

for a senior

Explains why AT_LEAST_ONCE forbids val assignment while still guaranteeing initialization, and uses a var as the workaround.

for a principal

Reasons about the lattice of guarantees, where UNKNOWN fits, and how the analysis composes with nested scope functions and exceptions.

## InvocationKind enum `kotlin.contracts.InvocationKind` has exactly four values. The shared meaning of `callsInPlace(block, kind)` is: the lambda is **called in place** — synchronously, within the function call, never escaping to be run later/elsewhere. The `kind` refines *how many times*. | Kind | Times | val assignable inside? | val/var initialized after? | |------|-------|------------------------|----------------------------| | `EXACTLY_ONCE` | exactly 1 | yes | yes | | `AT_LEAST_ONCE` | 1..n | no (might run twice → double-assign) | yes | | `AT_MOST_ONCE` | 0 or 1 | yes (still ≤ once) | no (might run 0 times) | | `UNKNOWN` | any | no | no | ## Why each rule holds - **EXACTLY_ONCE** is the sweet spot: single assignment + guaranteed run ⇒ both "assign a val" and "read it after" are legal. This is what `run`, `let`, `with`, `also`, `apply` use. - **AT_LEAST_ONCE** guarantees initialization (it ran), but because it might run *more* than once, assigning a `val` inside would risk a second assignment — illegal. Use a `var` instead, or just rely on "initialized after". `repeat` uses this kind. - **AT_MOST_ONCE** allows the `val` assignment (never more than once), but since it might run *zero* times, the compiler cannot prove the value is set afterward — reading it post-call still requires it to be definitely assigned by some other path. - **UNKNOWN** gives the weakest guarantee: only "in place". It is mostly useful so a contract author can still promise the lambda doesn't escape (e.g., for other flow facts) without committing to a count. ## Code illustration ```kotlin @OptIn(ExperimentalContracts::class) inline fun atMostOnce(block: () -> Unit) { contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) } if (Random.nextBoolean()) block() } val x: Int atMostOnce { x = 1 } // OK to assign (<= once) // println(x) // ERROR: might not be initialized (could run 0 times) @OptIn(ExperimentalContracts::class) inline fun atLeastOnce(block: () -> Unit) { contract { callsInPlace(block, InvocationKind.AT_LEAST_ONCE) } do { block() } while (Random.nextBoolean()) } var y: Int atLeastOnce { y = 1 } // var, fine println(y) // OK: definitely initialized (ran >= once) ``` ## Term reminders - **In place / does not escape**: not stored in a field, not passed to another thread, not run after the function returns. - **Definite assignment**: compiler proof a variable is set before read. - The default when you call `callsInPlace(block)` without a kind is `UNKNOWN`.

  • Which kind does `repeat` use and why not EXACTLY_ONCE?
    `AT_LEAST_ONCE` is not quite right either — `repeat(n)` runs the action n times where n could be 0, so it actually cannot promise EXACTLY_ONCE; in practice its body runs 0..n. The key point: only EXACTLY_ONCE permits val assignment, which is why repeat doesn't enable it.
  • If a function might call the lambda 0 or 1 times, why allow val assignment at all?
    Because single-assignment is still satisfied (never twice). The trade-off is the compiler can't prove the val is initialized afterward, so post-call reads remain restricted.

saying these in an interview costs you the question

  • Says AT_LEAST_ONCE allows val assignment (it doesn't — may run twice)
  • Says AT_MOST_ONCE guarantees the value is set afterward
  • Forgets that all kinds imply 'called in place / not escaping'
  • Thinks UNKNOWN is useless or means 'never called'
  • Confuses invocation count with the value the lambda returns

context