When is an `object` declaration initialized, and what guarantees does Kotlin give about thread-safety of that initialization?
answer
- Lazy: created on first access, not at startup
- Thread-safe via JVM class-init lock (<clinit> runs once)
- = initialization-on-demand holder, for free
- No @Volatile / double-checked locking needed
- Circular object init can deadlock
basics
~10 sThe object is created the first time something accesses it, not at program start. Kotlin makes sure this happens only once even if many threads hit it at the same time.
solid answer
~40 sAn `object` declaration is initialized **lazily on first access** and the initialization is **thread-safe**, with exactly-once semantics. On the JVM this is not magic: the object compiles to a class with a `public static final INSTANCE` field assigned in a `static { }` initializer. The JVM class-loading specification guarantees that a class's static initializer runs once, under a lock, the first time the class is initialized — so two threads racing to first-touch the object both see one fully-constructed instance with no torn state. Note 'first access' means first access to the object *class*, which can be triggered by reading any of its members. There is no double-checked-locking boilerplate to write and no `@Volatile` to manage — the class-loading mechanism provides the happens-before guarantee for free.
code
kotlin · 11 linesobject Counter {
init { println("init on first touch") }
private var n = 0
@Synchronized fun next(): Int = ++n
}
// First call triggers init AND is the only time it runs,
// even under concurrent access from many threads.
fun main() {
List(8) { Thread { Counter.next() } }.forEach { it.start() }
}go deeper
Knows it's created on first use and only once.
Explains lazy first-access plus thread-safe exactly-once init.
Names the JVM class-init mechanism (<clinit> lock, happens-before) and that method-level state still needs its own synchronization.
Discusses circular-init deadlocks, init-exception failure modes, and why this is the holder idiom in disguise.
## Lazy initialization A Kotlin `object` is **not** created when the program starts. It is created **on first access** — the first time code touches the object (reads a property, calls a function, or references the instance). Until then, no instance and no `init` side effects exist. ```kotlin object Heavy { init { println("Heavy initialized") } val data = loadBigThing() } fun main() { println("started") Heavy.data // <-- "Heavy initialized" prints HERE, not before } ``` ## Why it's thread-safe (the JVM mechanism) The compiler turns `object Heavy` into a JVM class roughly like: ``` class Heavy { public static final Heavy INSTANCE; static { INSTANCE = new Heavy(); /* run init blocks, property inits */ } } ``` The **JVM Specification (§5.5, class initialization)** guarantees that a class's static initializer (`<clinit>`) runs **exactly once**, and the JVM serializes initialization with an internal **initialization lock**. So if multiple threads access `Heavy` for the first time concurrently: - Only one thread runs the static initializer. - Others **block** until it finishes. - All threads then observe a fully-constructed `INSTANCE` with a proper **happens-before** relationship (no torn/partially-initialized reads). This is the textbook **initialization-on-demand holder** idiom, but you get it automatically — no `@Volatile`, no double-checked locking, no `synchronized` blocks. ## Contrast with the manual Java singleton The naive Java singleton (`if (instance == null) instance = new X()`) is **not** thread-safe; fixing it required `volatile` + double-checked locking or an enum/holder class. Kotlin's `object` removes that footgun entirely. ## Subtleties - **Initialization order / deadlock:** if object A's initializer references object B and B's references A, you can get a circular-initialization deadlock or a partially-initialized read. Avoid cross-object init dependencies. - **Exceptions in init:** if the static initializer throws, the JVM marks the class erroneous; later accesses throw `NoClassDefFoundError`/`ExceptionInInitializerError`. - **'Eager' feel:** because first access is often early (e.g., during DI wiring or top-level access), people sometimes assume it's eager. It is still strictly first-touch. ## Keywords/APIs to name `object`, `init`, `@Volatile` (what you'd otherwise need), `<clinit>` static initializer, `ExceptionInInitializerError`, and the class-loading happens-before guarantee.
- Does the thread-safety of *initialization* mean the object's methods are thread-safe too?No. Only the one-time construction is guarded. Mutable state inside the object still needs your own synchronization (e.g., `@Synchronized`, atomics, or a concurrent collection).
- What underlying JVM feature provides the guarantee?Class-initialization semantics from the JVM spec: the static `<clinit>` runs once under an initialization lock, giving a happens-before edge.
saying these in an interview costs you the question
- Saying objects are created eagerly at program startup
- Claiming Kotlin uses double-checked locking with @Volatile under the hood (it relies on class init)
- Believing the object's instance methods are automatically thread-safe
- Not knowing 'first access' triggers initialization