Null Safety

Reviewed & published by Brayan K

Kotlin is a modern, concise language whose signature feature is null safety — its type system eliminates the NullPointerExceptions that crash programs in other languages.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll understand nullable types, the safe call ?., the Elvis operator ?:, and when (rarely) to use !!.

What You'll Learn in This Lesson

1️⃣ Nullable Types and Safe Calls

In Kotlin, a regular type like String can never be null. To allow null, you add a question mark: String?. Once a value might be null, the compiler refuses to let you use it carelessly — you must handle the null case. The safe call operator ?. returns null instead of crashing.

fun main() {
    // A normal String can NEVER hold null:
    val name: String = "Ada"
    // name = null  // ❌ won't compile — null is not allowed here

    // Add a ? to the type to make it NULLABLE — now null is allowed:
    val middle: String? = null
    println(middle)               // -> null

    // The compiler stops you from using a nullable value unsafely:
    // println(middle.length)     // ❌ won't compile — middle might be null

    // Use ?. (safe call): returns null instead of crashing.
    println(middle?.length)       // -> null

    val nickname: String? = "Aces"
    println(nickname?.length)     // -> 4
}

The key insight: the line that would crash in other languages doesn't even compile here. You're forced to deal with null up front, which is exactly why Kotlin programs crash so much less.

2️⃣ Elvis, !!, and let

The Elvis operator ?: supplies a fallback when the left side is null. The not-null assertion !! forces a value to non-null and throws if you're wrong — use it sparingly. And ?.let { } runs a block only when the value isn't null.

fun main() {
    val input: String? = null

    // The Elvis operator ?: gives a fallback when the left side is null.
    val text = input ?: "default"
    println(text)                          // -> default

    // Combine safe call + Elvis to compute a safe length:
    val length = input?.length ?: 0
    println("Length is $length")           // -> Length is 0

    // !! forces a non-null value — use ONLY when you are certain.
    val sure: String? = "ok"
    println(sure!!.uppercase())            // -> OK

    // A common safe pattern: only act when not null
    val maybe: String? = "hi"
    maybe?.let { println("Got: $it") }     // -> Got: hi
}

The pattern value?.length ?: 0 is everywhere in real Kotlin: "the length, or 0 if it's missing". Lean on it.

Your turn. Fill in the TODO, then run and compare.

// 🎯 YOUR TURN — replace each TODO then run main()
fun main() {
    // A nullable middle name that might be null:
    val middleName: String? = null   // 👉 try changing this to "Rose" later

    // 1) Use the Elvis operator to default to "(none)" when null
    val display = middleName ?: "TODO"   // 👉 put "(none)" here

    // 2) Safely get the length, defaulting to 0 when null
    val len = middleName?.length ?: 0    // already done for you

    println("Middle: $display, length: $len")

    // ✅ Expected (with null):  Middle: (none), length: 0
}

Mini-Challenge: A Safe Profile Printer

Combine safe calls and the Elvis operator to print a profile that never crashes on missing data.

// 🎯 MINI-CHALLENGE: a safe profile printer.
// Save as Profile.kt and run main().
//
// 1. Make a nullable email: String? (set it to null OR an address).
// 2. Print "Email: <value>" using the Elvis operator to show
//    "not provided" when it is null.
// 3. Print the email length, defaulting to 0 when null,
//    using a safe call ?. combined with ?:
//
// ✅ Example output (email = null):
//    Email: not provided
//    Length: 0

fun main() {
    val email: String? = null
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Null Safety

ConceptKotlin Syntax
Nullable typeval x: String? = null
Safe callx?.length
Elvis (default)x ?: "fallback"
Safe call + defaultx?.length ?: 0
Not-null assertionx!!
Run if not nullx?.let { ... }

🎉 Lesson Complete!

Practice quiz

How do you declare a String variable that is allowed to hold null?

  • val s: String = null
  • val s: String? = null
  • val s: Nullable<String> = null
  • val s: String! = null

Answer: val s: String? = null. Adding ? to the type (String?) makes it nullable; a plain String can never be null.

What does middle?.length evaluate to when middle is null?

  • null
  • 0
  • It throws a NullPointerException
  • An empty string

Answer: null. The safe call ?. returns null instead of crashing when the receiver is null.

What is the result of the expression (null as String?) ?: "default"?

  • null
  • an empty string
  • "default"
  • a NullPointerException

Answer: "default". The Elvis operator ?: supplies the right-hand value when the left side is null.

What does the not-null assertion operator !! do?

  • Returns null safely when the value is absent
  • Converts the value to a nullable type
  • Forces a non-null value and throws NPE if it is actually null
  • Provides a default value

Answer: Forces a non-null value and throws NPE if it is actually null. !! asserts non-null and throws a NullPointerException at runtime if you are wrong.

What does name?.length ?: 0 return when name is null?

  • null
  • 0
  • It does not compile
  • The length of an empty string

Answer: 0. The safe call yields null, then Elvis substitutes the fallback 0.

Which block runs only when a nullable value is non-null?

  • value!!.let { }
  • value?.let { }
  • value.run { }
  • value.also { } always runs

Answer: value?.let { }. value?.let { } combines a safe call with let so the block runs only for a present value.

After if (x != null) { ... }, how can you use x inside the block?

  • Only with !!
  • Only with ?.
  • Directly, because Kotlin smart-casts x to non-null
  • You must reassign it first

Answer: Directly, because Kotlin smart-casts x to non-null. A null check smart-casts a local val to its non-null type inside that block.

Why is Kotlin null safety often described as moving a problem to compile time?

  • It removes all exceptions from the language
  • The compiler forces you to handle null before the code compiles
  • It makes every type nullable by default
  • It runs a null checker only at runtime

Answer: The compiler forces you to handle null before the code compiles. Nullable types force handling at compile time rather than failing as a runtime NPE.

What does a chain like a?.b?.c return if b is null?

  • The value of c
  • null
  • A NullPointerException
  • The value of a

Answer: null. A chain of safe calls short-circuits to null as soon as any link is null.

Which is the recommended default for handling a nullable value?

  • Always use !! for brevity
  • Use ?. and ?: and reserve !! for rare certain cases
  • Never check for null
  • Cast away the nullability

Answer: Use ?. and ?: and reserve !! for rare certain cases. Prefer safe calls and Elvis; treat !! as a code smell used only when truly certain.

Continue this course

Frequently asked questions

What problem does null safety actually solve?

In many languages any reference can be null, so calling a method on a value that happens to be null crashes the program with a NullPointerException (NPE) — famously called 'the billion-dollar mistake'. Kotlin moves this problem from runtime to compile time: a regular type like String can never be null, and a nullable type like String? forces you to handle the null case before the code will even compile. The result is far fewer crashes in production.

What's the difference between ?. and !! ?

The safe call operator ?. evaluates to null instead of crashing when the receiver is null — so value?.length is null if value is null. The not-null assertion operator !! does the opposite: it tells the compiler 'trust me, this is not null', and if you are wrong it throws a NullPointerException at runtime. Use ?. (often with the Elvis operator ?:) by default, and reserve !! for the rare cases where you are absolutely certain a value is present.

What does the Elvis operator ?: do?

The Elvis operator provides a default value for a nullable expression. In a ?: b, if a is non-null the whole expression is a; if a is null the expression is b instead. It is commonly combined with a safe call, as in name?.length ?: 0, which reads 'the length of name, or 0 if name is null'. It's named Elvis because ?: looks like his hairstyle turned sideways.

How does smart casting work with nullable types?

When you check a nullable value with an if (x != null) condition, Kotlin 'smart casts' x to its non-null type inside that block, so you can use it directly without ?. or !!. This works for local vals because the compiler can prove the value did not change. Smart casts also apply after is checks (covered later), making conditional code clean and safe.