How and when are top-level properties initialized, and what risks arise from their initialization order across files on the JVM?
answer
- Initializers run in file facade <clinit>, lazily on first use
- Same file: declaration order, deterministic
- Across files: no guaranteed order
- const val is inlined - no ordering hazard
- Use by lazy or co-locate dependencies
basics
~20 sTop-level properties with initializers run when their file's generated class is first loaded, top to bottom. Across different files the order is not guaranteed, so one top-level value depending on another in a different file can be null or default if loaded too early.
solid answer
~50 sA top-level val/var with a non-const initializer is compiled into the file facade's static initializer (clinit). It runs once, lazily, when that facade class is first touched (class loading is lazy on the JVM). Within one file properties initialize in declaration order, so later ones may use earlier ones. Across files, each facade loads independently the first time it is used, so there is no defined cross-file initialization order - a top-level property referencing another file's top-level property can observe an uninitialized (default/zero/null) value if that other facade hasn't loaded yet. const val is different: it is a compile-time constant inlined at use sites, so it has no initialization-order hazard. To make order explicit and safe, prefer by lazy { } for derived top-level state, or keep interdependent values in the same file in dependency order.
code
kotlin · 8 lines// File: Settings.kt
val base = 100
val scaled = base * 2 // safe: same file, after base
// derived value that depends on possibly-later state -> lazy
val label: String by lazy { "scaled=$scaled" }
const val VERSION = "1.0" // compile-time constant, always availablego deeper
Knows top-level properties with initializers get a value before first use and read top-to-bottom in a file.
Explains they run in the file's static initializer lazily on first use and same-file order is deterministic.
Identifies the cross-file ordering hazard, distinguishes const val, and prescribes by lazy / co-location fixes.
Reasons about <clinit>, class-loading laziness, circular-facade partial initialization, and sets patterns to keep global state safe and testable.
## When do top-level properties initialize? The compiler places non-`const` top-level property initializers into the static initializer block (`<clinit>`) of the file's facade class (e.g. `ConfigKt`). The JVM runs `<clinit>` **once**, the first time the class is *initialized* - which is **lazy**: it happens on first active use (first call to a top-level function in that file, first read of one of its properties, etc.), not at program start. ## Within a single file: deterministic Properties initialize in **textual (declaration) order**. A later property may safely reference an earlier one: ```kotlin // File: Config.kt val base = 10 val doubled = base * 2 // safe: base is already initialized ``` ## Across files: no guaranteed order Each `.kt` file becomes a **separate** facade class, each with its **own** `<clinit>`, triggered independently. There is **no global ordering** of top-level initializers between files. If `A.kt`'s top-level property reads `B.kt`'s top-level property and `B`'s facade hasn't been initialized yet, you can read a **default value** (`0`, `false`, or `null`) instead of the intended one. ```kotlin // File: B.kt val root = "https://example.com" // File: A.kt val endpoint = "$root/api" // RISK with circular references between facades ``` The JVM does eagerly initialize a referenced class on first access, mitigating many simple cases - but **circular** references between facades can still expose partially-initialized state. ## const val avoids the hazard ```kotlin const val ROOT = "https://example.com" // inlined at every use site ``` A `const val` (primitives and `String` only, value known at compile time) is **inlined** into bytecode at each usage, so it never participates in `<clinit>` ordering and is always available. ## Safe patterns - Use **`by lazy { }`** for derived top-level values: the delegate computes on first access, after dependencies are reachable. - Keep interdependent top-level properties **in the same file**, in dependency order. - Prefer `const val` for true compile-time constants. - Avoid **circular** top-level dependencies across files. ```kotlin val endpoint: String by lazy { "$root/api" } // computed on first use ``` ## Why interviewers ask It probes understanding that Kotlin's clean top-level syntax still rests on JVM class-loading and `<clinit>` semantics - a subtle source of null/default bugs in configuration and registry code.
- Why does const val avoid initialization-order problems?It is a compile-time constant inlined directly at every use site, so it never runs in a static initializer.
- How does by lazy make a derived top-level property safer?It defers computation to first access, by which time the referenced declarations are reachable, avoiding reading uninitialized defaults.
Each file is a separate machine that only powers on when first used; expecting machine A to read a dial on machine B before B is switched on can give you a blank reading.
saying these in an interview costs you the question
- Claiming top-level properties init at program startup eagerly in a fixed global order
- Saying cross-file initialization order is guaranteed
- Treating const val and val as identical in initialization
- Ignoring <clinit>/class-loading as the underlying mechanism
- Recommending circular top-level dependencies across files