Phase 1 · FoundationsModule 3~38 min read

Null Safety

Kotlin's flagship feature — eliminate null-pointer errors with nullable types and safe operators.

What you'll learn

This is the feature that made Kotlin famous. Its type system tracks which values can be null and forces you to handle them — turning the most common crash in programming into a compile-time error you fix before your app ever runs.

By the end you'll be able to:

  • Understand why null causes so many bugs, and how Kotlin prevents them
  • Declare nullable and non-nullable types
  • Use the safe-call ?. and Elvis ?: operators
  • Handle nulls with let, and know why to avoid !!

The billion-dollar mistake

The inventor of the null reference, Tony Hoare, called it his "billion-dollar mistake" — because NullPointerExceptions (crashes from using a value that turned out to be null) have cost the industry untold time and money. In most languages, any reference can secretly be null, and you only find out when it crashes at runtime.

Kotlin's solution is radical and simple: a type can't hold null unless you explicitly say so. The compiler then makes sure you never use a nullable value without checking it first.

Nullable types

By default, a type is non-nullable — String can never be null. To allow null, you add a ?: String?. That single character changes everything, because the compiler now refuses to let you use it carelessly:

Nullable.kt
val name: String = "Sara"      // non-nullable: can NEVER be null
val nickname: String? = null   // nullable: the ? allows null

println(name.length)           // 4 — always safe
println(nickname?.length)      // null — the ? protects you

Key idea

This is the whole idea: name (type String) is guaranteed to have a value, so name.length is always safe. nickname (type String?) might be null, so the compiler won't let you write nickname.length — you must handle the null case.

The safe-call operator ?.

The safe-call operator ?. calls a method or accesses a property only if the value isn't null. If it is null, the whole expression evaluates to null instead of crashing. You can chain them, and the first null short-circuits the rest:

SafeCall.kt
val city: String? = null

// a safe call chain: if any link is null, the result is null — no crash
println(city?.uppercase())          // null
println(city?.uppercase()?.length)  // null

val real: String? = "Beirut"
println(real?.uppercase())          // BEIRUT

The Elvis operator ?:

Often you want a default when something is null. The Elvis operator ?: (it looks like Elvis's hair and eyes tilted sideways) returns its left side if it's non-null, otherwise the right side:

Kotlin's null-safety operators

?.

Safe call

Call a member only if not null — else the whole expression is null.

?:

Elvis

Provide a default value when the left side is null.

!!

Not-null assertion

Force non-null — throws if it's actually null. Avoid it.

as?

Safe cast

Cast, returning null instead of throwing on failure.

Elvis.kt
val name: String? = null

val display = name ?: "Guest"    // if name is null, use "Guest"
println(display)                 // Guest

val length = name?.length ?: 0   // 0 when name is null
println(length)                  // 0

Tip

Combine them: value?.property ?: default reads as "the property if value isn't null, otherwise the default." This pattern appears constantly in real Kotlin code.

!! and let

The not-null assertion !! tells the compiler "trust me, this isn't null" — but if you're wrong, it throws a NullPointerException, exactly the crash Kotlin is trying to prevent. Meanwhile, let runs a block only when the value is non-null, giving you a clean, safe scope:

NotNull.kt
val name: String? = "Sara"

// !! forces non-null — throws if it's actually null (use sparingly!)
println(name!!.length)           // 4

// 'let' runs its block only if the value is non-null
name?.let {
    println("Hello, $it")        // 'it' is the value, guaranteed non-null
}

Avoid !!

Every !! is a place your program can crash. Treat it as a code smell — there's almost always a better tool (?., ?:, let, or an early return). Reserve !! for the rare cases where you've truly proven the value can't be null.

Recap & quick check

Key takeaways

  • By default types can't be null; add ? to make a type nullable (String?).
  • The compiler forces you to handle nullable values — turning runtime crashes into compile-time errors.
  • ?. (safe call) accesses a member only if non-null, else the result is null (and chains short-circuit).
  • ?: (Elvis) supplies a default when the left side is null.
  • let runs a block only when non-null; avoid !! (it re-introduces the crash Kotlin prevents).

Quick check

1. What does the ? in String? mean?

2. What does value?.length return if value is null?

3. What does the Elvis operator ?: do?

4. Why should you avoid !!?

5. What does 'name?.let { ... }' do?

Excellent — you've mastered Kotlin's signature feature. Next up: Module 4 — Operators, Ranges & Type Checks.