skip to content

Nullable Receivers

Extensions can be declared on a nullable receiver, which is how String?.isNullOrEmpty() and orEmpty() are callable without checking first. The body inspects this == null itself, which surprises people who expect the safe-call operator to be mandatory.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What is a nullable receiver in Kotlin, and how does it let String?.isNullOrEmpty() be called on a null value without a NullPointerException?

level: juniorimportance: must knowfreq 70%

answer

  1. Receiver type is T? not T
  2. Extensions resolved statically, not virtual dispatch
  3. this == null is legal inside the body
  4. isNullOrEmpty / orEmpty from stdlib
  5. No ?. or !! needed at call site

basics

~20 s

It 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 s

A 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 lines
kotlin
fun 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"))  // hi

go deeper

for a junior

Knows isNullOrEmpty()/orEmpty() can be called on null and names the receiver type T?.

for a middle

Explains static resolution and that this is nullable inside the body, enabling this == null.

for a senior

Connects to member-vs-extension resolution and smart-cast after the null check.

for a principal

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

context

open as a page

Inside an extension function declared on String?, what is the type of `this`, and how do you safely call String members on it?

level: middleimportance: must knowfreq 55%

basics

~20 s

Inside the body, this is nullable (String?). To use String members you must first check it is not null; after that check the compiler treats this as non-null, so you can call methods like length.

open as a page

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

level: middleimportance: should knowfreq 25%

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.

open as a page

How does the compiler decide between a member call and a nullable-receiver extension, and how does that explain calling an extension on null?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Extensions are picked at compile time from the declared type, while members are looked up on the real object at runtime. Because an extension call just passes the value as an argument, the value can be null, unlike a member call which needs a real object.

open as a page

When should you design an extension function with a nullable receiver T? rather than a non-null receiver, and what are the trade-offs?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use a nullable receiver when the function gives a sensible answer for null too — like turning null into an empty string. Avoid it when null has no meaning for the operation, so callers are forced to handle null themselves.

open as a page