skip to content

Explain the output of `val s: String? = null; println(s?.isNullOrEmpty())` versus `println(s.isNullOrEmpty())`. Why do they differ?

level: middleimportance: should knowfreq 25%

answer

  1. ?. short-circuits to null, skips the call
  2. Direct call lets the nullable-receiver fn handle null
  3. ?. changes Boolean to Boolean?
  4. Never ?. a nullable-receiver extension
  5. orEmpty(): drop the ?. too

basics

~10 s

Writing s?.isNullOrEmpty() prints null, because the safe call skips the function when s is null. Writing s.isNullOrEmpty() prints true, because the function is built to run on null and reports it as empty.

solid answer

~40 s

`isNullOrEmpty()` is an extension on a nullable receiver, so you should call it **directly**: `s.isNullOrEmpty()` runs the function with `this == null`, returning `true`. Using a safe call `s?.isNullOrEmpty()` is a mistake here: `?.` short-circuits to `null` when `s` is null, so the function never runs and the expression's type becomes `Boolean?` with value `null`. So the two print `true` and `null` respectively. The lesson: don't combine `?.` with a function that already accepts a nullable receiver — it both defeats the purpose and changes the result type from `Boolean` to `Boolean?`, which can break downstream logic like `if (s?.isNullOrEmpty())` (won't compile / needs `== true`).

code

kotlin · 5 lines
kotlin
val s: String? = null
println(s.isNullOrEmpty())   // true  (function runs with this == null)
println(s?.isNullOrEmpty())  // null  (?. skips the call)
println(s.orEmpty())          // ""   (correct)
println(s?.orEmpty())         // null (wrong — ?. skipped it)

go deeper

for a junior

Knows isNullOrEmpty() is called directly on a nullable value.

for a middle

Explains ?. short-circuits and changes the type to Boolean?, contrasting both outputs.

for a senior

Generalizes the rule: never ?. a nullable-receiver extension; explains downstream type breakage.

for a principal

Would lint/ban ?. on known nullable-receiver APIs and document the convention for the team.

## The two calls ```kotlin val s: String? = null println(s.isNullOrEmpty()) // true println(s?.isNullOrEmpty()) // null ``` ## Why `s.isNullOrEmpty()` is `true` `isNullOrEmpty()` is declared on `CharSequence?`. Calling it **directly** passes `s` (null) into the function. Its body is `this == null || this.length == 0`, and `this == null` is true, so it returns `true`. **This is the intended usage.** ## Why `s?.isNullOrEmpty()` is `null` The **safe-call operator `?.`** means: *if the left side is null, skip the call and produce `null`; otherwise call*. Since `s` is null, the whole expression short-circuits to `null` **without ever invoking** `isNullOrEmpty()`. The expression's static type also changes from `Boolean` to `Boolean?`. ## Why it matters The changed type bites you downstream: ```kotlin if (s?.isNullOrEmpty()) { } // ERROR: Boolean? not allowed in if-condition if (s?.isNullOrEmpty() == true) { } // compiles, but logic is now subtly different ``` With the correct direct call: ```kotlin if (s.isNullOrEmpty()) { } // Boolean — clean ``` ## Rule of thumb If a function already takes a **nullable receiver** (you can tell from names like `isNullOr...`, `orEmpty`, `orNull` or from its signature), **call it directly** — never prefix it with `?.`. Reserve `?.` for members and **non-null-receiver** extensions. ## Related trap `s.orEmpty()` returns `""` for null; `s?.orEmpty()` returns `null` for null (skipped) — exactly the opposite of what you want. Same fix: drop the `?.`.

  • What is the static type of s?.isNullOrEmpty()?
    Boolean?, because the safe call can yield null when s is null.
  • Does s?.isNullOrEmpty() ?: true 'fix' it?
    It compiles and gives true for null, but it's redundant and confusing; the idiomatic fix is the direct call s.isNullOrEmpty().

saying these in an interview costs you the question

  • Insisting you must use ?. for safety on isNullOrEmpty()
  • Saying both calls print true
  • Not noticing the result type becomes Boolean?
  • Using s?.orEmpty() expecting an empty string

context