skip to content

by lazy

by lazy computes a value on first access and caches it, with a thread-safety mode you can dial down when the property is confined to one thread. Interviewers compare it with lateinit: lazy is for val and self-initializing, lateinit for var assigned from outside.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What does `val x by lazy { ... }` do in Kotlin, and when does the lambda run?

level: juniorimportance: must knowfreq 78%

answer

  1. Computed once on first read, then cached
  2. Backed by Lazy<T> delegate via `by`
  3. val only (getter, no setter)
  4. Default mode is SYNCHRONIZED (thread-safe)
  5. Great for expensive / order-sensitive init

basics

~10 s

It 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 lines
kotlin
class 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; cached

go deeper

for a junior

Knows it computes once on first read and caches the result; can write a basic val x by lazy { ... }.

for a middle

Explains the Lazy<T> delegate, why it's val-only, and contrasts it with lateinit and eager init.

for a senior

Adds default SYNCHRONIZED thread-safety, exception-retry semantics, and when laziness helps with init order.

for a principal

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)

context

open as a page

Explain the three `LazyThreadSafetyMode` values (SYNCHRONIZED, PUBLICATION, NONE) and when you'd pick each.

level: middleimportance: should knowfreq 58%

basics

~20 s

They control how a lazy value behaves with many threads. SYNCHRONIZED locks so only one thread computes it (the safe default). PUBLICATION may compute in several threads but keeps the first result. NONE has no safety and is for single-thread use only.

open as a page

When would you choose `by lazy` over `lateinit var`, and how do their initialization and threading semantics differ?

level: middleimportance: should knowfreq 64%

basics

~10 s

Use by lazy when the value is read-only and you want it computed automatically the first time it's needed. Use lateinit var when something external assigns the value later, like a framework injecting it.

open as a page

If a `by lazy` initializer throws an exception on first access, what happens on the next access? Does the answer depend on the thread-safety mode?

level: seniorimportance: should knowfreq 34%

basics

~10 s

If the first computation fails with an exception, the value is not stored. The next time you read the property, Kotlin runs the block again and tries to produce the value once more.

open as a page

How is `by lazy` implemented under the hood, and what runtime/memory cost does it add compared to an eagerly-initialized `val`?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

Each by lazy property creates a small helper object that holds the block and the computed value. Reading the property goes through that object and checks if the value exists yet, which costs a little more memory and a tiny check on each read.

open as a page