Null Safety
How the type system removes NullPointerException — nullable types, the safe call and Elvis operators, the not-null assertion, smart casts and when they are allowed, lateinit, and the Nothing type.
7 questions
JuniorTheoryVery commonWhat does the ? in String? mark, and what does !! actually do?
What does the ? in String? mark, and what does !! actually do?
Nullability is part of the type: String and String? are different types, and the compiler rejects a null where a non-null type is expected. !! converts nothing — it throws NullPointerException when the value is null.
Common mistakes
- ✗Calling
!!a conversion rather than a throw-if-null assertion - ✗Treating
StringandString?as one type carrying a flag - ✗Taking the compiler's null check for a lint warning, not an error
Follow-up questions
- →Where is
!!genuinely justified, and what would you write instead? - →How does a Kotlin nullable type look to Java code calling it?
JuniorTheoryCommonWhen is a lateinit var the right choice over a nullable var?
When is a lateinit var the right choice over a nullable var?
lateinit fits a non-null value set after construction — an injected field. Its type stays non-null, so reads need no ?.; an early read throws UninitializedPropertyAccessException. A nullable var fits when absence is real.
Common mistakes
- ✗Putting
lateiniton avalor on a primitive type - ✗Confusing
lateinitwithby lazy, which computes on first read - ✗Expecting an early read to yield
nullrather than throw
Follow-up questions
- →How do you test whether a
lateinitproperty has been initialized yet? - →Why can a primitive
Intproperty not be declaredlateinit?
JuniorTheoryCommonHow do the safe call ?. and the Elvis operator ?: work together?
How do the safe call ?. and the Elvis operator ?: work together?
?. invokes the member only on a non-null receiver; otherwise the whole expression is null, so a?.b?.c stops at the first missing link. ?: gives the fallback — in a?.b ?: d the right side runs only when the left is null.
Common mistakes
- ✗Believing
?.catches an exception instead of short-circuiting tonull - ✗Expecting the Elvis right-hand side to be evaluated eagerly
- ✗Thinking a broken link mid-chain still runs the rest of
a?.b?.c
Follow-up questions
- →How would you make
?:throw instead of returning a fallback value? - →What is the type of
a?.bwhenbis declared as a non-nullInt?
MiddleDebuggingCommonA null check passes yet the smart cast refuses to compile — why, and how do you fix it?
A null check passes yet the smart cast refuses to compile — why, and how do you fix it?
A smart cast needs proof the value cannot change between check and use. A mutable property may be reassigned by another thread, so it is refused. Read it into a local val, which does smart-cast; name?.length ?: 0 also works.
Common mistakes
- ✗Thinking a smart cast works on any
var, a class property included - ✗Reaching for
!!instead of copying the value into a localval - ✗Believing a property with a custom getter can be smart-cast
Follow-up questions
- →Why does a property with a custom getter also refuse to smart-cast?
- →Would the same code compile if
namewere a localvarin the function?
MiddleTheoryCommonHow does Kotlin's null safety differ from Java's wrapper-type idiom Optional?
How does Kotlin's null safety differ from Java's wrapper-type idiom Optional?
Kotlin puts absence into the type — String? — so the compiler enforces it at every use, with no wrapper allocated. Optional is an opt-in class for return values; fields and parameters stay unguarded, and it can itself be null.
Common mistakes
- ✗Thinking
String?compiles into anOptionalwrapper on the JVM - ✗Forgetting that a reference to an
Optionalcan itself be null - ✗Treating
?as a lint hint rather than a compiler-enforced type
Follow-up questions
- →Why is
Optionaldiscouraged for fields and method parameters in Java? - →Which Kotlin idiom replaces a chain of
Optional.map(...).orElse(...)?
MiddleTheoryOccasionalWhat is Kotlin's Nothing type, and what is it actually good for?
What is Kotlin's Nothing type, and what is it actually good for?
Nothing is the type with no values: an expression of that type never completes normally. throw and TODO() are typed Nothing, so x ?: throw E() type-checks. It is a subtype of every type, so such a call fits anywhere.
Common mistakes
- ✗Confusing
NothingwithUnit, which does have a single value - ✗Calling
Nothinga supertype rather than a subtype of every type - ✗Missing that
throwis an expression whose type isNothing
Follow-up questions
- →What type does a bare
nullhave, and how doesNothing?explain it? - →Why does
emptyList()infer asList<Nothing>yet fit aList<String>?
SeniorTheoryOccasionalKotlin is said to have no NPEs — where does that claim honestly break down?
Kotlin is said to have no NPEs — where does that claim honestly break down?
The compiler guarantees only what it can prove. It cannot know the nullability of unannotated Java, so a platform type slips unchecked into a non-null type and the NPE lands there; nor is a mutable property provably stable, so it never narrows.
Common mistakes
- ✗Believing the compiler treats an unannotated Java type as nullable
- ✗Assuming a platform type forces a null check before it may be used
- ✗Trusting a Java getter's non-null type without checking at the boundary
Follow-up questions
- →How do nullability annotations on the Java side change a platform type?
- →Why does an NPE from a platform type often surface far from the Java call?