Which standard-library functions rely on `callsInPlace` contracts, what invocation kind does each use, and how does that affect what you can write inside their lambdas?
answer
- run/let/with/also/apply/synchronized = EXACTLY_ONCE
- repeat = AT_LEAST_ONCE (var ok, val no)
- forEach/map/filter: per-element, no val-assign superpower
- EXACTLY_ONCE is what unlocks val assignment
- kind must match real runtime behavior
basics
~20 sScope functions like run, let, with, also, apply use 'exactly once', so you can set a val inside them. repeat uses 'at least once', so a val can't be set there but a var can.
solid answer
~40 sThe scope functions `run`, `with`, `let`, `also`, and `apply` all declare `callsInPlace(block, InvocationKind.EXACTLY_ONCE)`. That is why you can assign a `val` inside any of them and read it afterward. `repeat(times) { }` declares `AT_LEAST_ONCE` for its action (it runs the action in a loop), so you may **not** assign a `val` inside but you can assign a `var` and treat it as definitely initialized after — though note `repeat(0)` semantics mean you should be cautious about real initialization guarantees. The synchronization helper `synchronized` also uses `EXACTLY_ONCE`. Functions whose lambda may not run, like `takeIf`/`takeUnless` predicates, or collection lambdas like `forEach`/`map`, generally do **not** declare EXACTLY_ONCE because the call count varies, so val assignment is not enabled there.
go deeper
Knows run/let allow val init; unsure about repeat and the others.
Lists the EXACTLY_ONCE scope functions and knows repeat behaves differently.
States the kind for each (including synchronized and repeat's AT_LEAST_ONCE) and explains the resulting val/var rules.
Reasons about which custom collection/loop helpers should carry which kind and the maintenance risk of mismatched contracts across the stdlib surface.
## Functions with EXACTLY_ONCE These carry `callsInPlace(block, InvocationKind.EXACTLY_ONCE)`, enabling `val` assignment and post-call reads inside their lambdas: - `run { }` and `T.run { }` - `with(receiver) { }` - `T.let { }` - `T.also { }` - `T.apply { }` - `synchronized(lock) { }` ```kotlin val config: Config synchronized(lock) { config = load() } println(config) // OK: synchronized is EXACTLY_ONCE ``` ## Functions with AT_LEAST_ONCE - `repeat(times) { action }` declares its action as `AT_LEAST_ONCE`. Because it may run more than once, a `val` cannot be assigned inside; a `var` can, and is considered initialized afterward: ```kotlin var last: Int = 0 repeat(3) { last = it } // var ok ``` ## Functions that typically do NOT use callsInPlace (for count) - Collection operators: `forEach`, `map`, `filter`, `onEach` — the lambda runs per element (0..n), and these are designed around iteration, so they don't grant val-assignment via EXACTLY_ONCE. - `takeIf` / `takeUnless` — predicate runs once, but the *result* is conditional; they generally don't enable post-call val initialization the way scope functions do. ## Practical consequences - Choosing `run`/`let`/`with` for one-shot setup gives you the val-initialization superpower for free. - Reaching for `repeat` does not — use a `var` or restructure. - For your own loop-like helper, pick the kind that matches reality (`AT_LEAST_ONCE` for loops), and remember only `EXACTLY_ONCE` unlocks val assignment. ## Why it matters in interviews Knowing *which* function carries *which* kind explains common compiler errors like 'Captured values initialization is forbidden due to possible reassignment' inside a `repeat`, versus the clean behavior inside `run`.
- Why does `repeat` not let you assign a val inside, but `run` does?`repeat` uses AT_LEAST_ONCE (the action may run many times), which would double-assign a val. `run` uses EXACTLY_ONCE, so single assignment is guaranteed.
- Does `forEach` carry an EXACTLY_ONCE contract?No. The lambda runs once per element (zero to many times), so it does not grant the EXACTLY_ONCE val-assignment behavior.
saying these in an interview costs you the question
- Claims forEach/map use EXACTLY_ONCE
- Says you can assign a val inside repeat
- Doesn't know synchronized carries a contract
- Thinks apply/also lack the contract because they return the receiver
- Believes any scope function would work for one-time val init regardless of kind