What problem does Delegates.notNull() solve, and how does it behave if you read it before assigning a value?
answer
- notNull() = late-set non-null var without initializer
- Fills the gap lateinit can't: primitives
- Read-before-set throws IllegalStateException
- Reassignable after first set
- lateinit preferred for references (cheaper, .isInitialized)
basics
~10 sDelegates.notNull() lets you declare a non-null var without giving it a value right away. You assign it later. If you read it before setting it, you get an exception instead of a silent null.
solid answer
~40 sDelegates.notNull<T>() returns a delegate for a non-null `var` that has no initial value — useful when the value is known only after construction (e.g. injection, lifecycle callbacks). It is an alternative to `lateinit var` for cases where `lateinit` cannot be used: notably **primitive types** (`Int`, `Long`, `Boolean`, etc.), which `lateinit` does not support. Reading the property before any assignment throws `IllegalStateException("Property X should be initialized before get.")`. After you assign a value it behaves like a normal non-null var and can be reassigned. Compared with `lateinit`, the delegate version boxes the value and goes through delegate get/set, so it has slight overhead — prefer `lateinit` for object references and reserve `notNull()` for primitives.
code
kotlin · 12 linesimport kotlin.properties.Delegates
class Config {
var maxRetries: Int by Delegates.notNull() // primitive: lateinit not allowed
}
fun main() {
val c = Config()
// c.maxRetries -> IllegalStateException before set
c.maxRetries = 3
println(c.maxRetries) // 3
}go deeper
Knows notNull() defers initialization of a non-null var and throws if read too early.
Identifies the primitive-type gap vs lateinit and names the IllegalStateException behaviour.
Explains the boxing/delegate overhead, the lack of an isInitialized check, and when to prefer lateinit.
Frames late-init choices around API/contract clarity, considers constructor injection or nullable+validation alternatives, and the cost of read-before-init bugs in production.
## The problem Sometimes you need a **non-null** property whose value is set *after* the object is constructed — for example a config injected by a framework, or a value set in an `onCreate`/`init` callback. You cannot write `var x: Int` without an initializer, and you do not want a nullable `Int?` that forces `!!`/`?.` everywhere. ## Two solutions - `lateinit var` — language feature; works for **non-primitive, non-nullable** reference types. - `Delegates.notNull<T>()` — a delegate that fills the gap where `lateinit` cannot be used, **most importantly primitives** (`Int`, `Long`, `Double`, `Boolean`, ...), which `lateinit` forbids. ## Behaviour ```kotlin import kotlin.properties.Delegates var threshold: Int by Delegates.notNull() fun main() { // println(threshold) // would throw IllegalStateException: "Property threshold should be initialized before get." threshold = 42 println(threshold) // 42 threshold = 7 // reassignable like any var } ``` - **Read before assignment** → throws `IllegalStateException` with message "Property <name> should be initialized before get." - **After assignment** → behaves like a normal non-null var; can be reassigned freely. ## notNull() vs lateinit — comparison | | `lateinit var` | `Delegates.notNull()` | |---|---|---| | Primitives (Int/Boolean...) | **Not allowed** | **Allowed** | | Reference types | Allowed (preferred) | Allowed but boxes/overhead | | `::prop.isInitialized` check | Yes | No such check | | Mechanism | Backing field, no boxing | Delegate object + boxing | | Read before set | `UninitializedPropertyAccessException` | `IllegalStateException` | ## Guidance - Use `lateinit` for object references — cheaper, supports `.isInitialized`. - Use `Delegates.notNull()` when the type is a **primitive** and you still want non-null late initialization. - Both signal a programming error (read-before-init) loudly rather than returning null.
- Why use notNull() instead of lateinit for an Int?lateinit does not support primitive types; notNull() provides late, non-null initialization for Int/Long/Boolean etc.
- What exception type is thrown on read-before-set?IllegalStateException with message 'Property X should be initialized before get.' (lateinit instead throws UninitializedPropertyAccessException).
- Can you check whether a notNull() property was initialized, like ::prop.isInitialized?No — the isInitialized check only works for lateinit properties, not for Delegates.notNull().
A reserved seat with a name card: it's guaranteed to be filled, but sit down before anyone arrives and you get thrown out.
saying these in an interview costs you the question
- Claiming notNull() works on val (it requires var)
- Saying lateinit supports primitives (it does not)
- Thinking read-before-set returns null instead of throwing
- Confusing the exception with UninitializedPropertyAccessException (that's lateinit's)
- Believing ::prop.isInitialized works for notNull()