RizTech Academy logo
RizTech Academy
Null SafetyLesson 2 of 425 min

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

  1. Declare a String? holding a value and one holding null. Print ?.length on each and confirm the second is null, not a crash.
  2. Confirm the type of name?.length is Int? (the IDE will tell you, or try assigning it to an Int and read the error).
  3. Build the Customer/Address classes and print customer?.address?.city for a full chain and for a null customer.
  4. Write a function taking String?, check if (name != null), and use .length inside without ?.. Confirm it compiles.
  5. Rewrite the same function with early return (if (name == null) return) and note the body reads without ?..
  6. Make a List<String?> with some nulls and drop them with filterNotNull().
  7. Explain the difference between List<String?> and List<String>? in one sentence each, then write a value of each type.

Official documentation

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