What is a nullable receiver in Kotlin, and how does it let String?.isNullOrEmpty() be called on a null value without a NullPointerException?
answer
- Receiver type is T? not T
- Extensions resolved statically, not virtual dispatch
- this == null is legal inside the body
- isNullOrEmpty / orEmpty from stdlib
- No ?. or !! needed at call site
basics
~20 sIt is an extension function written on a type that may be null (like String?). Because the function is defined for the null case too, you can call it on null, and inside it checks whether this is null.
solid answer
~40 sA nullable receiver is an extension function whose receiver type is nullable, e.g. fun String?.isNullOrEmpty(). Extension functions are resolved statically at compile time based on the declared type, not via a virtual dispatch on a live object, so the compiler can permit a call even when the value is null. Inside the body, this is of type String? and can be compared with `this == null` without a prior null check. The standard library defines isNullOrEmpty() and orEmpty() this way, so `value.isNullOrEmpty()` compiles and runs safely even when value is null. This contrasts with member functions, which require a non-null instance and would throw NullPointerException (or need ?.) when the receiver is null.
code
kotlin · 6 linesfun String?.orDefault(d: String): String =
if (this == null) d else this // smart-cast to String
val x: String? = null
println(x.orDefault("none")) // none, no NPE
println("hi".orDefault("none")) // higo deeper
Knows isNullOrEmpty()/orEmpty() can be called on null and names the receiver type T?.
Explains static resolution and that this is nullable inside the body, enabling this == null.
Connects to member-vs-extension resolution and smart-cast after the null check.
Discusses API design trade-offs of nullable receivers (ergonomics vs hidden null handling) and when to prefer them.
## What "receiver" means An **extension function** lets you add a function to a type without modifying it. The type before the dot is the **receiver**. In `fun String.shout(): String = this.uppercase()`, `String` is the receiver type and `this` refers to the value the function was called on. ## Nullable receiver A **nullable receiver** is simply an extension function whose receiver type is a nullable type like `String?`. The standard library does this: ```kotlin public inline fun CharSequence?.isNullOrEmpty(): Boolean { return this == null || this.length == 0 } ``` Because the receiver type is `String?` (a `CharSequence?`), you are allowed to call the function on a value that is `null`. ## Why this does NOT throw Extension functions are **resolved statically** (at compile time) based on the **declared/static type** of the expression — they are *not* virtual member calls dispatched on a live object. The compiler effectively turns `value.isNullOrEmpty()` into something like `StringsKt.isNullOrEmpty(value)`, passing `value` as the first argument. Calling a static-style function with a `null` argument is perfectly legal. Inside the body, `this` has type `String?`, so `this == null` is a normal comparison — no dereference happens until you smart-cast to non-null. ```kotlin val name: String? = null println(name.isNullOrEmpty()) // true — no NPE, no ?. needed ``` Contrast with a **member** function: `name.length` would not compile (`name` is `String?`), and `name!!.length` would throw because `!!` forces a dereference. ## Smart-cast inside the body After `if (this != null)`, the compiler **smart-casts** `this` from `String?` to `String`, so you can call non-null members: ```kotlin fun String?.lengthOrZero(): Int { if (this == null) return 0 return this.length // smart-cast to String here } ``` ## Key standard-library examples - `CharSequence?.isNullOrEmpty()` / `CharSequence?.isNullOrBlank()` - `String?.orEmpty()` returns `""` when null - `Collection?.orEmpty()`, `Collection?.isNullOrEmpty()` - `Map?.orEmpty()`, `Array?.orEmpty()` These exist precisely so you can chain on possibly-null values without `?.` or `!!`.
- If a member function and a nullable-receiver extension had the same name, which wins?Members always win over extensions when the receiver is non-null and the member is applicable; the extension is only considered when no member matches.
- Why can't a regular member function be called on null without ?.?Member calls are virtual and need a live, non-null instance to dispatch on; calling on null dereferences and throws NPE, so the compiler forbids it on a nullable type.
Like a voicemail greeting that still plays even when nobody picks up — the function is designed to handle the 'nobody home' (null) case itself.
saying these in an interview costs you the question
- Claiming the function uses reflection or a try/catch to avoid NPE
- Saying you still need ?. when calling isNullOrEmpty()
- Thinking extension functions are virtual/overridable like members
- Believing this is guaranteed non-null inside the body