December Code

Kotlin null safety interview questions, with answers

Null safety is Kotlin's best-known feature and the first topic in most Kotlin and Android interviews. The type system separates values that can be null from values that cannot, and the compiler refuses to let code use a possibly null value without handling the null first. Interviewers check that you know the operators that do that handling, when a null check turns a nullable value into a safe one, and where nulls can still slip in.

The answers below cover nullable types, the safe call and Elvis operators, !!, smart casts, lateinit and lazy, collections with nulls and values from Java, with code compiled with kotlinc 2.3 and run on the JVM. Then take the free Kotlin diagnostic — ten questions across every Kotlin topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.What is null safety in Kotlin?

    In short: Types are non-null by default; a type that may hold null is written with a question mark, String?, and the compiler requires null to be handled before such a value is used.

    In Kotlin, String and String? are different types. A variable of type String can never hold null, so it can be used freely. A String? may be null, and the compiler allows only null-safe operations on it: the safe call ?., the Elvis operator ?:, an explicit null check, or the !! assertion. Assigning null to b below is fine because b is a String?, and b?.length evaluates to null instead of crashing. This moves most NullPointerExceptions from run time to compile time, which was the main reason Android teams adopted Kotlin.

    var a: String = "x"
    var b: String? = "y"
    b = null
    println(b?.length)
    // null
    println(a.length)
    // 1
  2. 2.What do the safe call ?. and the Elvis operator ?: do in Kotlin?

    In short: a?.b evaluates to null instead of throwing when a is null; x ?: y gives x unless it is null, in which case it gives y.

    The safe call operator short-circuits: if the value on its left is null, the whole expression is null and nothing on its right is evaluated, so chains such as user?.address?.city stop at the first null. The Elvis operator supplies a fallback for null: m?.get("zip") ?: "n/a" below yields n/a because the map has no zip key. The right side of ?: can also be return or throw, which makes val x = y ?: return a compact way to leave a function early. Together they replace most explicit if (x != null) checks.

    val m: Map<String, String>? =
        mapOf("city" to "Pune")
    val c = m?.get("zip") ?: "n/a"
    println(c)
    // n/a
  3. 3.What does the !! operator do in Kotlin, and why avoid it?

    In short: !! asserts that a value is not null and converts it to the non-null type; if the value is null, it throws a NullPointerException.

    The not-null assertion operator tells the compiler to trust you: env!! turns a String? into a String, and if env is null at run time, it throws a NullPointerException on that line, as the code below does. It is the one operator that deliberately reintroduces the crash Kotlin's type system exists to prevent, so code reviews treat each !! as a question. Better options are a safe call with a fallback, as in the first half of the code, an early return with ?: return, or requireNotNull(x) { "message" }, which throws an IllegalArgumentException with an explanation instead of a bare NPE.

    val env = System.getenv("NOPE")
    val n = env?.length ?: 0
    println(n)
    // 0
    val m = env!!.length
    // throws NullPointerException
  4. 4.What is a smart cast in Kotlin?

    In short: After a check such as x != null or x is String, the compiler treats x as the narrower type in the code the check protects, with no explicit cast.

    When the compiler can see that a value has been checked, it narrows its type automatically. In desc below, the when branch is Int lets x + 1 compile, and is String allows x.uppercase(), without any cast; the null branch comes first, so later branches know x is not null. Smart casts work after if checks, inside when branches and after early returns, such as if (x == null) return. They apply only when the compiler can prove the value cannot change between the check and the use, which is always true for local val variables.

    fun desc(x: Any?) = when (x) {
        null -> "nothing"
        is Int -> "int ${x + 1}"
        is String -> x.uppercase()
        else -> "other"
    }
    
    fun main() {
        println(desc(4))
        // int 5
        println(desc("kt"))
        // KT
        println(desc(null))
        // nothing
    }
  5. 5.What are lateinit and lazy in Kotlin?

    In short: lateinit var declares a non-null property that is assigned later, before first use; val by lazy computes a value on first access and caches it.

    Some properties cannot be set in the constructor, such as a view in an Android activity or a value injected by a framework. lateinit var lets such a property keep a non-null type: it starts unset, and reading it before assignment throws an UninitializedPropertyAccessException, which is clearer than a null. It works only on var properties of non-primitive types. val cache by lazy { ... } delegates the property to a Lazy object that runs the block on first access and returns the stored result afterwards, as the single init line below shows. lazy is thread-safe by default and suits expensive values that may never be needed.

    class Repo {
        lateinit var url: String
        val cache by lazy {
            println("init")
            mutableListOf<Int>()
        }
    }
    
    fun main() {
        val r = Repo()
        r.url = "db"
        r.cache.add(1)
        r.cache.add(2)
        println(r.cache)
        // init
        // [1, 2]
    }
  6. 6.How do you remove or skip nulls in Kotlin collections?

    In short: Use filterNotNull to drop nulls from a collection, mapNotNull to transform and drop null results in one step, and orEmpty to treat a null collection as empty.

    A List<String?> can hold nulls, and filterNotNull() returns a List<String> without them, so later code gets a non-null element type. mapNotNull combines a map with that filter: below, toIntOrNull returns null for "x", and mapNotNull keeps only the successful conversions, so the sum is 10. For a collection that is itself nullable, orEmpty() returns an empty collection in place of null, so it can be iterated without a check. firstOrNull, getOrNull and singleOrNull are the null-returning counterparts of functions that would otherwise throw.

    val raw = listOf("3", "x", "7")
    val nums = raw.mapNotNull {
        it.toIntOrNull()
    }
    println(nums.sum())
    // 10
  7. 7.How does Kotlin handle null values that come from Java code?

    In short: Values from Java have platform types, written String!, whose nullability is unknown; declare them as nullable in Kotlin to get the compiler's checks back.

    Java does not say whether a method may return null, so Kotlin gives such values a platform type, shown as String! in error messages. A platform type can be used as either String or String?, and if it is treated as non-null while actually null, the program throws at that point. The safe practice is to assign Java results to an explicitly nullable variable, as home below, and handle the null with ?: or ?., which is what System.getenv needs, since it returns null for an unset variable. Java annotations such as @Nullable and @NonNull are understood by the compiler and turn platform types into proper Kotlin types.

    val home: String? =
        System.getenv("DC_NONE")
    println(home ?: "unset")
    // unset

How the diagnostic asks it

One question from the Kotlin bank, exactly as a sitting would show it. The bank has 4 on null safety and 30 across Kotlin.

Null Safety · mediumKT-005

What does this Kotlin code print?

val words: List<String?> = listOf("hi", null, "kotlin")
val lens = words.map { it?.length ?: -1 }
println(lens)
  1. 1[2, -1, 6]correct
  2. 2[2, null, 6]
  3. 3[2, 0, 6]
  4. 4It throws a NullPointerException on the null element

For each element, it?.length is the string's length when it is not null and null when it is, without throwing. The Elvis operator ?: returns its left side if that is not null, and its right side otherwise, so the null element maps to -1 while the others map to 2 and 6. The list prints as [2, -1, 6]. [2, null, 6] is what it?.length alone would give. [2, 0, 6] assumes a null string has length 0. ?. exists precisely so that a null receiver does not throw a NullPointerException.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Kotlin questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated