skip to content

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%

answer

  1. T? when null has a meaningful, single answer
  2. T when null is a bug — force caller to handle
  3. Name it OrEmpty / isNullOr... to signal tolerance
  4. Nullable receiver hides null; reduces type pressure
  5. Members can shadow your extension later

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.

solid answer

~50 s

Choose a nullable receiver (`T?`) when the operation has a **well-defined meaning for null** and you want callers to chain without `?.` — classic cases are normalizers (`orEmpty()`), predicates (`isNullOrEmpty()`, `isNullOrBlank()`), and safe-default helpers. The benefit is ergonomic, null-tolerant call sites and centralized null handling. The cost: it **hides** the fact that the receiver may be null, can mask logic errors (you silently treat null as 'empty'), and weakens the type system's pressure to deal with null at the boundary. Prefer a non-null receiver (`T`) when null is genuinely invalid for the operation, so the compiler forces the caller to resolve nullability first (via `?.`, `!!`, or `?:`). Also beware that members win over extensions, so a `T?` extension could be shadowed later. Rule of thumb: nullable receiver for 'null is a valid, meaningful input'; non-null for 'null is a bug here'.

go deeper

for a junior

Recognizes orEmpty-style helpers accept null but can't articulate the design rule.

for a middle

States 'use T? when null has a meaningful result' and gives an example.

for a senior

Weighs ergonomics vs hidden-null trade-offs and uses naming to signal tolerance.

for a principal

Sets team guidance: where null is a bug vs valid input, and accounts for member-shadowing and API evolution.

## The decision Use a **nullable receiver** when **null is a legitimate, meaningful input** to the operation and there's an obvious correct behavior for it. Use a **non-null receiver** when **null is not meaningful** — you want the type system to force the caller to handle it. ## Good candidates for `T?` receivers - **Normalizers**: `fun String?.orEmpty(): String = this ?: ""` — null collapses to a neutral value. - **Null-aware predicates**: `isNullOrEmpty()`, `isNullOrBlank()` — the question 'is it empty or absent?' is well-defined for null. - **Safe display/format helpers**: `fun Instant?.formatOr(d: String) = this?.toString() ?: d`. These let call sites stay flat: ```kotlin val label = user.nickname.orEmpty().uppercase() // no ?. needed ``` ## When to prefer a non-null receiver `T` - The operation is undefined for null (e.g. `String.repeat(n)` — repeating 'nothing' is meaningless). - You want to **push null handling to the boundary** so bugs surface, not hide. - Domain logic where a null receiver indicates a programming error. With a `T` receiver, the caller must narrow first: ```kotlin val r = name?.let { it.repeat(2) } // forced to decide what null means ``` ## Trade-offs | Aspect | Nullable receiver `T?` | Non-null receiver `T` | |---|---|---| | Call-site ergonomics | High (no `?.`) | Lower (need `?.`/`?:`) | | Null-bug visibility | Hidden — null silently handled | Forced — compiler nags | | Reusability on null values | Yes | No | | Risk of masking errors | Higher | Lower | ## Other considerations - **Member shadowing**: members win over extensions, so a future member could quietly replace your `T?` extension. Don't rely on shadowable extensions in evolving libraries. - **Naming**: signal null tolerance in the name (`...OrEmpty`, `isNullOr...`) so readers know the receiver may be null. - **Don't overuse**: making everything `T?` erodes the value of Kotlin's null safety — the type system can no longer warn you where null is unexpected. ## Heuristic Ask: 'Is there a single, obviously-correct result when `this` is null?' If yes, a nullable receiver is ergonomic and safe. If no, use a non-null receiver and let the caller decide.

  • Why might overusing nullable receivers be harmful?
    It hides nullability everywhere, so the compiler stops flagging places where null is genuinely unexpected, masking real bugs.
  • How should naming reflect a nullable receiver?
    Use names like orEmpty/orNull/isNullOrBlank so callers immediately see the function tolerates a null receiver.

saying these in an interview costs you the question

  • Defaulting every extension to a nullable receiver for 'safety'
  • Using T? when null has no meaningful result, silently swallowing it
  • Ignoring that this hides null from the type system
  • Not signaling null tolerance in the function name

context