Skip to Content
Course content

153: Null Safety in Collections

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've talked about nullable types, but things get a bit more nuanced when you throw collections into the mix. The biggest point of confusion for most developers—and it took me a while to wrap my head around it too—is the difference between a nullable list and a list containing nullable elements. They look similar, but they require completely different handling strategies.

Handling the "Maybe" Guest List

Let's build a small RSVP processor. Imagine we're managing a guest list for a tech meetup. Sometimes the entire list is missing from the database (the list itself is null), and sometimes a guest signed up but didn't provide their name (an element in the list is null). This gives us a type like List<String?>?.

val guestList: List<String?>? = listOf("Alice", null, "Bob", null, "Charlie")

See that? The outer ? means the whole list might be null. The inner ? means the names inside could be null. If I want to print the names, I can't just loop through them blindly. I have to handle both layers of "maybe."

Where I tripped over nullable elements

I wanted to calculate the total number of characters across all provided names to estimate how much ink I'd need for name tags. At first, I tried this:

// My first (wrong) attempt
val totalChars = guestList?.map { it.length }?.sum()

The compiler immediately yelled at me. Why? Because it inside that map refers to a String?. You can't call .length on a nullable string without a safe call or a null check. I thought, "Easy, I'll just use a safe call," and did this:

val totalChars = guestList?.map { it?.length }?.sum()

This actually compiles, but it's not what I wanted. it?.length returns an Int?. Now my map has produced a List<Int?>. While Kotlin's sum() is clever enough to handle some of this, I'm now dealing with a collection of nullable integers, which is a recipe for bugs later in the pipeline if I need to do more complex math.

Filtering the noise out

The cleaner way to handle this is to strip out the nulls entirely before I start transforming the data. Kotlin provides a fantastic extension function for this: filterNotNull(). This function doesn't just remove the nulls; it actually changes the type of the collection from List<T?> to List<T>.

Here is how I actually solved it:

val guestList: List<String?>? = listOf("Alice", null, "Bob", null, "Charlie")

val totalChars = guestList
    ?.filterNotNull() // This transforms List<String?> to List<String>
    ?.map { it.length } // Now 'it' is definitely a String, no ? needed
    ?.sum() ?: 0 // Handle the case where the entire guestList was null

println("Total characters: $totalChars")

By using filterNotNull(), I've pushed the null-handling to the very beginning of the chain. This makes the rest of the logic—the mapping and the summing—much cleaner because I'm working with guaranteed values. It's a pattern I use constantly: filter the junk out early, then process the clean data.




📋 Practical Task

Exercise: Cleaning the Product Inventory

You are building a warehouse tool. You have a list of product weights, but the data is messy. Some weights are missing (null), and the entire inventory list might be null if the database connection fails.

Your Task: Write a function called calculateTotalWeight that takes a List<Double?>? as an argument. The function should:

  1. Return 0.0 if the list itself is null.
  2. Filter out all null weights within the list.
  3. Calculate the sum of the remaining weights.
  4. Return the final sum as a Double.
fun calculateTotalWeight(weights: List<Double?>?): Double {
    // Your code here
}

// Test cases to verify your logic:
// calculateTotalWeight(listOf(1.2, null, 3.4, null, 0.4)) should return 5.0
// calculateTotalWeight(null) should return 0.0
// calculateTotalWeight(listOf(null, null)) should return 0.0
Rating
0 0

There are no comments for now.

to be the first to leave a comment.