Nullable in Kotlin: How to Work with Null Values Without Pain? Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

What Hides Behind the Question Mark? Diving into Nullable in Kotlin

\n\n

Have you ever written code in Java and caught a NullPointerException at the worst possible moment? If so — you know how painful it is. Kotlin developers solved this problem once and for all by making null safety not just a feature, but a language philosophy. But beginners are often scared by the question: "In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them?" (this is exactly how the most popular question on Stack Overflow sounds).

\n\n

Today we will break down all the idioms for working with nullable: from simple ?. to tricky let and magical !!. And most importantly — we'll look under the hood to understand how it works at the bytecode level. Let's go!

\n\n

Quick Primer: What is Nullable?

\n\n

In Kotlin, types are divided into two camps: nullable (can be null) and non-nullable (cannot). If you write var name: String = null — the compiler will throw an error. But var name: String? = null — that's fine. The question mark is a marker that says: "Hey, compiler, I know this might be empty, don't yell at me."

\n\n

But how do you work with this? Simply checking for null with if is boring and not Kotlin-like. Let's look at the real tools.

\n\n

The Lifesaving Trio: ?, ?: and ?.

\n\n

Kotlin offers three pillars on which all nullable work rests. Remember them — they will save you hours of debugging.

\n\n

1. Safe call operator (?.)

\n\n

This is the most frequent guest in your code. The safe call operator allows you to call a method or property only if the object is not null. If the object is null — the operator returns null, and no exception.

\n\n
val user: User? = null\nval name: String? = user?.name  // name = null, no error
\n\n

Under the hood, this turns into bytecode that does a regular null check and conditional call. No magic, just safety.

\n\n

2. Elvis operator (?:)

\n\n

Elvis is your friend when you need to provide a default value. If the left side is not null — take it, otherwise — take what's on the right.

\n\n
val name: String? = null\nval displayName: String = name ?: "Guest"  // "Guest"
\n\n

Elvis can also be used for early return from a function:

\n\n
fun getUserName(user: User?): String {\n    val name = user?.name ?: return "Anonymous"\n    return name\n}
\n\n

3. Not-null assertion (!!)

\n\n

This is the rebel operator. It says: "I'm sure this is not null, and I'm ready to die (meaning, throw an NPE) if I'm wrong." Use it with caution, only when you are 100% sure.

\n\n
val user: User? = getUserFromDatabase() // we are sure it exists\nval name: String = user!!.name
\n\n

Under the hood, !! compiles into a regular check that throws NullPointerException on null. Nothing complicated, but dangerous.

\n\n

Idiomatic Way: let, run, apply and Company

\n\n

Now we move to the most interesting part. The Stack Overflow question is exactly about this: how to idiomatically transform nullable values. The answer — extension functions from the standard library.

\n\n

let — King of Nullable Transformations

\n\n

The let function executes a block of code only if the object is not null. Inside the block, the object is available as it (or you can rename it).

\n\n
val user: User? = getUser()\nuser?.let { \n    println("Username: ${it.name}")\n    // Here it is guaranteed not null\n}
\n\n

This is the idiomatic way to perform an action only on a non-null value. Without let, you would have to write:

\n\n
if (user != null) {\n    println("Username: ${user.name}")\n}

Blogs

Book Recommendations