What does `val x by lazy { ... }` do in Kotlin, and when does the lambda run?
answer
- Computed once on first read, then cached
- Backed by Lazy<T> delegate via `by`
- val only (getter, no setter)
- Default mode is SYNCHRONIZED (thread-safe)
- Great for expensive / order-sensitive init
basics
~10 sIt delays creating a value until the first time you read the property. The block runs once, the result is remembered, and every later read returns that same stored value.
solid answer
~30 s`by lazy { ... }` uses the standard-library `lazy()` function to create a `Lazy<T>` delegate. The property's getter is backed by that delegate: the first time you read the property, the lambda is executed and the result is cached in the delegate's `value`. All subsequent reads return the cached value without re-running the lambda. It only works on a `val` (read-only), since the value is computed once and never reassigned. Common uses: expensive objects you may not always need, breaking initialization order/cycles, or values that depend on something not yet available at construction time. By default it is thread-safe (`LazyThreadSafetyMode.SYNCHRONIZED`).
code
kotlin · 10 linesclass Service {
val client by lazy {
println("building client")
HttpClient() // runs once, on first use
}
}
val s = Service() // nothing printed yet
s.client // prints "building client"
s.client // prints nothing; cachedgo deeper
Knows it computes once on first read and caches the result; can write a basic val x by lazy { ... }.
Explains the Lazy<T> delegate, why it's val-only, and contrasts it with lateinit and eager init.
Adds default SYNCHRONIZED thread-safety, exception-retry semantics, and when laziness helps with init order.
Frames trade-offs: per-access null-check/sync cost vs. eager init, GC of the captured lambda, and API/library-design implications of exposing lazy values.
## What `by lazy` is `by` is Kotlin's **delegated property** keyword: instead of the property storing a value directly, reads/writes are forwarded to a *delegate* object. `lazy { ... }` is a standard-library factory function that returns a `Lazy<T>` instance, and `Lazy<T>` provides the `getValue` operator that the `by` mechanism calls. ```kotlin val config: Config by lazy { println("computing") loadConfig() // runs ONCE, on first access } ``` ## Lifecycle - **Before first read:** the lambda has not run; no value exists. - **First read:** the lambda executes, its result is stored in the delegate. - **Every later read:** the stored value is returned; the lambda is **never** re-run. You can check whether it has been computed via `isInitialized()` on the `Lazy` instance (you need a reference to the delegate, obtainable with the `::config.getDelegate()`-style reflection, or by holding the `Lazy` yourself). ## Why only `val`? `by lazy` requires a `val`. A lazy value is computed once and cached; reassigning it makes no sense, so the `Lazy<T>` delegate only exposes a getter (`getValue`), not a setter. ## Common reasons to use it - **Deferring expensive work** (DB connection, parsing) until actually needed. - **Avoiding null / `lateinit`** for values that need other state to exist first. - **Breaking initialization-order problems** inside a class, since the block runs lazily rather than at construction time. ## Contrast with alternatives - `lateinit var` — mutable, no caching semantics, you must assign it manually; throws if read before assignment. - Eager `val x = expensive()` — computed at construction even if never used. Note: the lambda is captured as part of the delegate until first access, then released so it can be garbage-collected.
- Can you use `by lazy` on a `var`?No. `lazy()` returns a `Lazy<T>` that only provides `getValue`, so the compiler rejects it on a `var`. Lazy values are write-once.
- Does the lambda run again if it throws on first access?With the default SYNCHRONIZED mode, an exception is not cached: the next access retries the lambda. The value is only cached on successful completion.
Like a vending slot that's empty until someone first presses the button; it makes the snack once and then hands out that same snack on every later press.
saying these in an interview costs you the question
- Says the lambda runs at object construction time
- Thinks the block re-runs on every property access
- Claims `by lazy` works on `var`
- Confuses it with `lateinit` (which has no caching and is mutable)