Nullable types and the safe call operator
Now the practical tools. You have a String? — a value that might be null — and you need to use it.
Kotlin gives you a small set of operators for this, and the first and most important is the safe
call, ?.. This lesson covers it and its natural partners for working with nullable values.
The safe call operator ?.
The safe call says "call this, but only if the value is not null; otherwise give me null":
val name: String? = "Kavita"
println(name?.length) // 6
val missing: String? = null
println(missing?.length) // null — no crash, the whole expression is null
name?.length means: if name is null, the result is null; if it is not, call .length. It
never crashes — that is the point. Where Java would throw an NPE, Kotlin's ?. quietly produces
null and moves on.
The result type reflects this. name?.length is not Int — it is Int?, because it might be
null (when name is). The nullability flows through the expression, and the compiler keeps track.
Chaining safe calls
Safe calls chain, which is where they earn their keep. Imagine nested nullable data:
class Address(val city: String?)
class Customer(val address: Address?)
val customer: Customer? = Customer(Address("Pune"))
println(customer?.address?.city) // Pune
println(customer?.address?.city?.length) // 4
val empty: Customer? = null
println(empty?.address?.city) // null — stops at the first null, no crash
customer?.address?.city walks the chain, and the moment any link is null, the whole expression
short-circuits to null. In Java this would be three separate null checks nested inside each other —
the "staircase of doom". In Kotlin it is one readable line. This is the single most common use of
?. in real code: reaching into data that might be absent at any level.
Using a safe call with a method that has side effects
?. works with any call, including ones that do something rather than return a value:
val name: String? = null
name?.let { println("Name is $it") } // prints nothing — name is null
Here ?.let { } runs the block only if name is not null. You will meet let properly in the next
lesson; for now, note that ?. gates any action behind the non-null check, not just property
access.
Checking for null with if — and smart casts
Sometimes you want to branch on null explicitly. A plain if works, and Kotlin adds a clever
touch — the smart cast:
val name: String? = getName()
if (name != null) {
println(name.length) // no ?. needed! inside this block, name is known to be String
}
Inside the if (name != null) block, the compiler knows name cannot be null, so it lets you
treat it as a plain String — .length with no ?.. This is a smart cast: the compiler
narrows the type based on the check you just made. It is one of Kotlin's quiet delights — you check
once, and the compiler remembers within that scope.
Smart casts work with the early-return pattern too, which flattens code nicely:
fun greet(name: String?) {
if (name == null) return // handle the null case and leave
println("Namaste, ${name.length}-letter name: $name") // name is String from here on
}
After the return, the compiler knows name is non-null for the rest of the function — so the
whole body below reads without a single ?.. This "deal with null at the top, then work with a
guaranteed value" shape is idiomatic and worth adopting.
One caveat: smart casts only work when the compiler can prove the value has not changed
between the check and the use. A var that could be modified by another thread, or a property with
a custom getter, cannot be smart-cast — the compiler will tell you, and the fix is usually to copy
it into a local val first.
Nullable types in collections
The ? composes with everything, and where you put it changes the meaning:
val a: List<String?> = listOf("Pune", null, "Nagpur") // a list that may contain nulls
val b: List<String>? = null // a whole list that might be null
List<String?> is a list whose elements can be null; List<String>? is a list reference that
itself can be null. Read the ? position carefully — it is a common source of confusion. Kotlin
gives you tools for the first case, like filterNotNull():
val cities: List<String?> = listOf("Pune", null, "Nagpur", null)
println(cities.filterNotNull()) // [Pune, Nagpur] — a List<String>, nulls removed
Check your work
What ?. does. Calls the member only if the value is non-null; otherwise the expression is
null — never a crash.
The result type of name?.length where name is String?. Int? — nullability flows through.
What chained safe calls do at the first null. Short-circuit the whole expression to null.
What a smart cast is. After if (x != null), the compiler treats x as non-null within that
scope, so no ?. is needed.
The early-return pattern's benefit. Handle null at the top with return; the rest of the
function works with a guaranteed non-null value.
When a smart cast is not allowed. When the compiler cannot prove the value is unchanged between
check and use — e.g. a mutable var or a custom-getter property; copy to a local val.
The difference between List<String?> and List<String>?. Elements may be null, versus the
whole list reference may be null.
How to drop nulls from a list. filterNotNull().
Practice
- Declare a
String?holding a value and one holding null. Print?.lengthon each and confirm the second isnull, not a crash. - Confirm the type of
name?.lengthisInt?(the IDE will tell you, or try assigning it to anIntand read the error). - Build the
Customer/Addressclasses and printcustomer?.address?.cityfor a full chain and for a null customer. - Write a function taking
String?, checkif (name != null), and use.lengthinside without?.. Confirm it compiles. - Rewrite the same function with early return (
if (name == null) return) and note the body reads without?.. - Make a
List<String?>with some nulls and drop them withfilterNotNull(). - Explain the difference between
List<String?>andList<String>?in one sentence each, then write a value of each type.
Official documentation
- Kotlin — Null safety: safe calls —
?.in the authors' words. - Kotlin — Smart casts — When the compiler narrows a type for you.
- Kotlin standard library — filterNotNull — Dropping nulls from a collection.
Next: the Elvis operator, let, and using them together.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship