skip to content

Nullable & Generic Receivers

Extensions can be declared on a nullable receiver, so the function itself handles null, and on generic receivers, which is how also and takeIf work for every type. Recognizing these two shapes in the stdlib makes its design legible.

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

questions

5

What does it mean for an extension function to have a nullable receiver type like `fun String?.orEmpty()`, and why can you call it on a `null` value without a NullPointerException?

level: juniorimportance: must knowfreq 70%

answer

  1. Receiver type ends with `?`
  2. Static dispatch → first param is the receiver
  3. null passed as an argument, no instance method
  4. Inside body `this` is nullable — must null-check
  5. stdlib: orEmpty, isNullOrEmpty, Any?.toString()

basics

~20 s

The function is declared on a type that allows null, so you can call it even when the value is null. Inside, you check for null yourself. No crash, because no method is dispatched on the object.

solid answer

~40 s

Declaring `fun String?.orEmpty()` makes the receiver type `String?`, so the function accepts null. Extension functions are resolved statically and compiled to a static method taking the receiver as the first parameter, so calling on null just passes null in — there is no virtual dispatch on the instance, hence no NPE at the call site. Inside the body, `this` is of type `String?`, so you must null-check (`if (this == null) ...`) or use safe operators before touching members. Standard-library examples include `CharSequence?.isNullOrEmpty()`, `Collection?.orEmpty()`, and `Any?.toString()`. This lets you write fluent code like `name.orEmpty()` even when `name` may be null, moving the null handling into the extension instead of the call site.

code

kotlin · 8 lines
kotlin
fun String?.orEmpty(): String = this ?: ""

fun main() {
    val n: String? = null
    println(n.orEmpty())          // ""
    println("hi".orEmpty())       // "hi"
    println(n.toString())         // "null" (Any?.toString)
}

go deeper

for a junior

Knows you can call such an extension on null without crashing and must null-check inside.

for a middle

Explains static dispatch / receiver-as-first-parameter as the reason no NPE occurs.

for a senior

Advises when to choose a nullable vs non-null receiver and cites stdlib precedents (orEmpty, isNullOrEmpty).

for a principal

Frames API-design tradeoffs: null-tolerant defaults vs forcing callers to handle null at the boundary, and impact on call-site readability.

## What a nullable receiver is An **extension function** adds a function to an existing type without modifying it. The type before the dot is the **receiver type**. When that receiver type is **nullable** (ends with `?`), the extension can be called on a possibly-null value: ```kotlin fun String?.orEmpty(): String = this ?: "" val a: String? = null println(a.orEmpty()) // prints "" — NO NullPointerException ``` ## Why there is no NPE Extensions are **not** real members. The compiler resolves them **statically** (at compile time, based on the declared/static type of the expression) and compiles them to a **static method** whose first parameter is the receiver. So `a.orEmpty()` compiles roughly to `OrEmptyKt.orEmpty(a)`. Passing `null` as an argument is perfectly legal — **no method is invoked on the object**, so there is nothing to throw an NPE. Contrast with a **member** function: `a!!.length` would dereference the instance and crash if null. ## Inside the body, `this` is nullable Within the extension, `this` has the **nullable** type. You therefore must handle null explicitly: ```kotlin fun String?.shout(): String { if (this == null) return "" // smart-cast: below, this is String return this.uppercase() + "!" } ``` Useful operators inside such a body: - `?:` (Elvis) — provide a fallback for null. - `?.` (safe call) — call a member only if non-null. - `this == null` check enables a **smart cast** to the non-null type afterward. ## Standard-library examples - `fun CharSequence?.isNullOrEmpty(): Boolean` - `fun <T> Collection<T>?.orEmpty(): Collection<T>` - `fun Any?.toString(): String` — even `null.toString()` returns `"null"`. ## When to use it Use a nullable receiver when the extension is **meant** to give sensible behavior for null (a default, an empty result, a boolean). If null is genuinely invalid, prefer a **non-null** receiver so the caller is forced to handle null before calling.

  • If you declare `fun String.foo()` (non-nullable receiver), can you call `foo()` on a `String?`?
    No. The compiler requires a non-null receiver, so you must use `?.foo()`, `!!`, or smart-cast to non-null first; otherwise it is a compile error.
  • What is the type of `this` inside `fun String?.orEmpty()`?
    `String?` — nullable. You must null-check or use `?:`/`?.` before accessing members of `String`.

It is like a mailbox slot that accepts an empty envelope: the slot (function) still works even if there is no letter (value) inside — you decide what to do when it is empty.

saying these in an interview costs you the question

  • Claiming calling an extension on null throws an NPE
  • Saying `this` inside a nullable-receiver extension is non-null
  • Confusing extensions with overriding/virtual member dispatch
  • Thinking you can call a non-null-receiver extension on a nullable value directly
  • Believing the receiver is mutated in place rather than passed as an argument

context

open as a page

How are the scope functions `also` and `apply` declared as generic-receiver extensions, and how do their receiver/return shapes differ?

level: middleimportance: must knowfreq 65%

basics

~20 s

Both work on any type because they take a generic receiver T. also passes the object as it and returns it; apply makes the object the lambda's this and also returns it. Both return the original object.

open as a page

Inside `fun <T> T?.ifNull(default: T): T`, how do you correctly handle `this` being null, and what role do smart casts play?

level: middleimportance: should knowfreq 55%

basics

~10 s

Inside, this may be null, so test it. After if (this != null) the compiler narrows the type to non-null, letting you use it safely. Or just use Elvis: return this ?: default.

open as a page

Compare designing an extension with a nullable receiver `fun T?.foo()` versus a non-null receiver called via `?.foo()`. What are the semantic and API-design differences?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A nullable-receiver extension runs even when the value is null and decides what null means. With a non-null receiver and ?., the whole call is skipped when null and the result is null. They behave differently for null inputs.

open as a page

When does a generic-receiver extension need `reified`, and how would you write `fun <reified T> Any?.castOrNull(): T?`-style helpers correctly?

level: seniorimportance: should knowfreq 40%

basics

~20 s

You need reified only when the function must know the real type at runtime — to do is T, as? T, or T::class. The function must be inline. Then you can write helpers that safely cast or filter by type.

open as a page