Explain the output of `val s: String? = null; println(s?.isNullOrEmpty())` versus `println(s.isNullOrEmpty())`. Why do they differ?
answer
- ?. short-circuits to null, skips the call
- Direct call lets the nullable-receiver fn handle null
- ?. changes Boolean to Boolean?
- Never ?. a nullable-receiver extension
- orEmpty(): drop the ?. too
basics
~10 sWriting 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 linesval 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
Knows isNullOrEmpty() is called directly on a nullable value.
Explains ?. short-circuits and changes the type to Boolean?, contrasting both outputs.
Generalizes the rule: never ?. a nullable-receiver extension; explains downstream type breakage.
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