What are the exact restrictions on where `lateinit` can be used, and how do you defer initialization of a primitive like `Int`?
answer
- var, non-null, non-primitive, no custom accessors
- Primitives banned: null is the unset marker
- Delegates.notNull() for Int/Boolean/etc.
- notNull throws IllegalStateException
- Works on top-level and local vars too
basics
~20 slateinit only works on a mutable var with a non-null, non-primitive type and no custom getter/setter. For a primitive like Int, you can't use lateinit; instead use by Delegates.notNull<Int>(), which throws if read before being set.
solid answer
~40 s`lateinit` is allowed only on: a `var` (not `val`); a non-null type; a non-primitive type (so not `Int`, `Long`, `Double`, `Boolean`, `Char` — these are JVM primitives that can't carry an 'uninitialized' sentinel); a property without a custom getter or setter. It works on member, top-level, and local `var`s, and on constructor-injected fields it does NOT work (constructor params are already initialized). For primitives the idiomatic replacement is the standard delegate `kotlin.properties.Delegates.notNull<Int>()`: `var port: Int by Delegates.notNull()`. Reading it before assignment throws `IllegalStateException` ("Property X should be initialized before get"). The reason `lateinit` excludes primitives is implementation: the compiler uses a `null` reference as the not-yet-initialized marker, and primitives have no null representation on the JVM.
code
kotlin · 11 linesimport kotlin.properties.Delegates
class Config {
lateinit var host: String // ok: non-null reference
var timeout: Int by Delegates.notNull() // primitive -> delegate
}
val c = Config()
c.host = "localhost"
c.timeout = 30
println("${c.host}:${c.timeout}")go deeper
Recalls that lateinit needs a non-null var and can't be a val.
Lists every constraint and knows Delegates.notNull() handles primitives.
Explains WHY primitives are excluded (null sentinel on JVM) and the differing exception types.
Compares lateinit vs notNull vs lazy as initialization contracts and picks the right tool per scenario, weighing inspectability and exception semantics.
## The full rule set for `lateinit` `lateinit` may be applied only when ALL of these hold: - The property is a **`var`** (mutable). A `val` is single-assignment and must be initialized at its declaration/constructor. - The type is **non-null** (`String`, not `String?`). - The type is **not a primitive**: `Int`, `Long`, `Short`, `Byte`, `Double`, `Float`, `Boolean`, `Char` are excluded. - The property has **no custom getter or setter** — `lateinit` needs the default backing-field accessors. - It is **not** a constructor parameter (those are already assigned). It CAN be used on: member properties, top-level properties, and local `var`s inside a function body. ## Why primitives are excluded Under the hood, the compiler represents a not-yet-assigned `lateinit` reference using the JVM `null` value, and the generated accessor checks for `null` to decide whether to throw `UninitializedPropertyAccessException`. JVM primitives (`int`, `boolean`, …) cannot be `null` and have a default like `0`/`false`, so there is no way to distinguish "unset" from "legitimately zero". Hence primitives are forbidden. ## The primitive workaround: `Delegates.notNull()` ```kotlin import kotlin.properties.Delegates class Server { var port: Int by Delegates.notNull() fun configure(p: Int) { port = p } } val s = Server() // println(s.port) // IllegalStateException: Property port should be initialized before get. s.configure(8080) println(s.port) // 8080 ``` Note the differences from `lateinit`: - It uses a **delegate** (`by`), not a modifier. - Early access throws `IllegalStateException`, not `UninitializedPropertyAccessException`. - There is **no** `isInitialized` check available for a delegated property this way. ## Summary table | Need | Use | |------|-----| | Defer non-null **object** ref | `lateinit var x: Foo` | | Defer non-null **primitive** | `var x: Int by Delegates.notNull()` | | Compute lazily on first read (val) | `val x by lazy { ... }` |
- Does `Delegates.notNull()` support an `isInitialized` check like `lateinit`?No. The `::prop.isInitialized` syntax is specific to `lateinit` properties; the `notNull` delegate offers no such inspection.
- Can `lateinit` be on a property with a custom getter?No. `lateinit` requires the compiler's default backing-field accessors; a custom getter/setter is incompatible.
saying these in an interview costs you the question
- Saying you can use lateinit on Int with a cast or trick
- Not knowing the Delegates.notNull() alternative for primitives
- Claiming lateinit allows custom getters/setters
- Confusing UninitializedPropertyAccessException with IllegalStateException as interchangeable here
- Thinking lateinit can be a constructor parameter