How does `by lazy` work as a delegated property, and what does the `LazyThreadSafetyMode` argument control?
answer
- by lazy -> Lazy<T> delegate, getValue
- initializer runs once, then cached
- SYNCHRONIZED (default) / PUBLICATION / NONE
- val only; lateinit is for var
- NONE = fastest, single-thread only
basics
~10 sby lazy computes a value the first time you read the property, then caches it for later reads. The mode setting controls whether that first computation is safe when many threads read at once.
solid answer
~40 s`val x by lazy { compute() }` uses **property delegation**: the compiler stores a `Lazy<T>` instance and routes reads of `x` through its `getValue` operator. The initializer lambda runs **once**, on first access; the result is cached and returned on every later read. The `lazy()` factory takes an optional `LazyThreadSafetyMode`: `SYNCHRONIZED` (default) locks so only one thread runs the initializer — safe for concurrent first access; `PUBLICATION` allows multiple threads to run the initializer concurrently but atomically publishes the first result that wins; `NONE` does no synchronization and is fastest but only safe when access is single-threaded. `lazy` is for `val` only. It contrasts with `lateinit var`, which is a mutable, non-null reference you must assign before use (no caching/computation logic). Lazy is implemented as a stdlib class, not a language keyword.
code
kotlin · 4 linesval heavy by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("computing once")
expensiveInit()
}go deeper
Knows by lazy computes once on first read and caches the result.
Explains the three thread-safety modes and the lazy-vs-lateinit distinction.
Describes the Lazy<T> delegate and getValue mechanics and picks the right mode for a concurrency context.
Frames lazy as one of several stdlib delegates and reasons about publication semantics and memory visibility under concurrent first access.
## Delegated properties recap Kotlin lets a property delegate its get/set to another object using the **`by`** keyword. For a `val`, the delegate must provide `operator fun getValue(thisRef, property): T`. The compiler generates a hidden field holding the delegate and rewrites reads to call `getValue`. ## What `lazy` provides `lazy { ... }` is a stdlib factory returning a `Lazy<T>`. There is a `getValue` extension on `Lazy<T>`, so it plugs into delegation: ```kotlin val config: Config by lazy { loadConfig() } // loadConfig runs on first read of config ``` Behavior: - The **initializer lambda runs at most once**, the first time the property is read. - The computed value is **cached**; subsequent reads return it without re-running the lambda. - `isInitialized()` on the `Lazy` reports whether it has run. - It applies to **`val`** (read-only) properties. ## `LazyThreadSafetyMode` `lazy(mode) { ... }` accepts an enum that controls concurrency of the **first** initialization: - **`SYNCHRONIZED`** (default): uses a lock so that if multiple threads hit it simultaneously, exactly one runs the initializer and the rest wait and see that result. Correct for shared/concurrent first access. - **`PUBLICATION`**: multiple threads may run the initializer concurrently, but the value is set atomically; the first successfully computed value 'wins' and all callers see it. Use when the initializer is cheap/idempotent and you want to avoid locking. - **`NONE`**: no thread safety at all — lowest overhead, but if two threads initialize concurrently the behavior is undefined. Only use when you guarantee single-threaded access (e.g. UI-thread-only state). ```kotlin val cache by lazy(LazyThreadSafetyMode.NONE) { buildCache() } // single-thread only ``` ## `lazy` vs `lateinit` - `lazy`: `val`, value is **computed on demand and cached**, thread-safety configurable, can be a `val`. - `lateinit var`: a **mutable** non-null property you promise to assign before reading; reading before assignment throws `UninitializedPropertyAccessException`. No computation logic, no caching, only for `var` of non-primitive non-null types. ## Why it matters for the stdlib model 'Lazy initialization' is a general runtime concept; Kotlin expresses it not with a keyword but as a **delegate object** (`Lazy<T>`) chosen from the stdlib, demonstrating how delegation generalizes property behaviors (lazy, observable, map-backed) without new syntax.
- When would you choose NONE over SYNCHRONIZED?When the property is only ever accessed from a single thread (e.g. UI thread), to skip locking overhead.
- Can you use lazy on a var?No. lazy backs a read-only val; for a mutable property use lateinit var or Delegates.observable/vetoable.
saying these in an interview costs you the question
- Saying the lazy initializer runs on every read
- Claiming lazy works on var
- Confusing lazy (computes + caches) with lateinit (no computation)
- Thinking NONE is always safe
- Believing by lazy is a language keyword rather than a stdlib delegate