← Back to Kotlin Course | Chapter 5: Null Safety | Lesson 6 of 6

Null Safety Best Practices

Best practices for null safety are the smart habits that help you avoid empty-box surprises altogether, rather than just reacting to them.
Syntax
kotlin
val name: Type = value            // prefer non-null
val parsed = input?.toIntOrNull() ?: default

fun functionName(): List<Type> {   // empty list instead of null
    return emptyList()
}

Prefer Non-Null by Default

Design functions and classes so most properties and parameters are non-nullable, reserving ? only for cases where absence is truly meaningful, which keeps the rest of your code free of constant null checks.

Example: Prefer Non-Null by Default

markup
// Define a data class `Product`
data class Product(val name: String, val price: Double, val discountCode: String? = null)

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `product`, set to `Product("Book", 15.0)`
    val product = Product("Book", 15.0)
    // Print "${product.name}: $${product.price}, discount=${product.discountCode ?: "none"}" to the console, with a trailing newline
    println("${product.name}: $${product.price}, discount=${product.discountCode ?: "none"}")
}

Validate at the Boundary

When data comes from outside your program, such as parsed text or a network response, convert it into a proper non-null domain type as early as possible instead of passing raw nullable values deep into your logic.

Example: Validate at the Boundary

markup
// Define the function `parseAge` taking `input` and returning an Int
fun parseAge(input: String?): Int {
    // Return `input?.toIntOrNull() ?: error("Invalid or missing age: $input")` from this function
    return input?.toIntOrNull() ?: error("Invalid or missing age: $input")
}

// Entry point: execution of the program starts here
fun main() {
    // Print "Age: ${parseAge("42")}" to the console, with a trailing newline
    println("Age: ${parseAge("42")}")
}

Avoid Nullable Collections

Returning an empty list instead of a nullable list means callers can safely iterate or check size without an extra null check, simplifying calling code considerably.

Note: An empty list is far easier to work with than null for representing 'no results'.

Example: Avoid Nullable Collections

markup
// Define the function `findMatches` taking `query`, `items` and returning a `List<String>`
fun findMatches(query: String, items: List<String>): List<String> {
    // Return `items.filter { it.contains(query) }` from this function
    return items.filter { it.contains(query) }
}

// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `results`, set to `findMatches("kt", listOf("main.kt", "readme.md"))`
    val results = findMatches("kt", listOf("main.kt", "readme.md"))
    // Print "Found ${results.size} matches: $results" to the console, with a trailing newline
    println("Found ${results.size} matches: $results")
}

Reserve !! for Truly Impossible Nulls

Treat !! as a signal that something is architecturally wrong if it ever actually throws; use it only in the rare case where the surrounding logic guarantees non-null, and prefer safer operators everywhere else.

Example: Reserve !! for Truly Impossible Nulls

markup
// Entry point: execution of the program starts here
fun main() {
    // Declare a read-only variable `cache`, set to `HashMap<String, String>()`
    val cache = HashMap<String, String>()
    cache["greeting"] = "Hello"
    // Check whether `"greeting" in cache`
    if ("greeting" in cache) {
        // Print `cache["greeting"]!!` to the console, with a trailing newline
        println(cache["greeting"]!!)
    }
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Designing APIs that return nullable types when an empty collection or a sensible default would communicate the same information more safely.
  2. Sprinkling !! throughout a codebase as a quick fix instead of addressing why a value might unexpectedly be null in the first place.
  3. Not validating external data (network responses, user input) at the boundary of your program, letting nulls leak deep into business logic.
Chapter Summary
  • Prefer non-nullable types by default; only mark something nullable when absence is a genuinely meaningful case.
  • Validate and convert nullable external data (JSON, user input) into non-null domain types as early as possible.
  • Use ?., ?:, and safe scope functions instead of !! in almost all situations.
  • Favor an empty collection over a nullable collection so callers do not need extra null checks just to iterate.
🔒

Chapter Quiz — Complete all 6 topics to unlock

0/6 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.