skip to content

You have a property `val onClick: (() -> Unit)?`. How do you call it safely when it might be null, and what is the difference between `onClick?.invoke()` and `onClick()`?

level: juniorimportance: must knowfreq 70%

answer

  1. `?.invoke()` is the safe call form
  2. `f()` desugars to `f.invoke()` — needs non-null receiver
  3. Parens: `(() -> Unit)?` vs `() -> Unit?`
  4. var member can't smart-cast → must use ?.invoke()
  5. result type is `Unit?`

basics

~10 s

Use onClick?.invoke(). It runs the function only if it is not null, otherwise it does nothing. Calling onClick() directly would not compile because the type may be null.

solid answer

~40 s

A nullable function type like `(() -> Unit)?` cannot be invoked with normal call syntax `onClick()` because the call operator is not defined on a nullable type — the compiler rejects it. You must first deal with the null. The idiomatic way is the safe-call form `onClick?.invoke()`, which only calls `invoke` when the reference is non-null and otherwise evaluates to `null` (or `Unit?`). The parentheses-call sugar `onClick()` desugars to `onClick.invoke()`, and `.invoke` requires a non-null receiver. Alternatives: a null check (`if (onClick != null) onClick()` with a smart cast, only for `val`/locals), or `onClick?.let { it() }`. For a default, `(onClick ?: {})()` substitutes an empty lambda.

code

kotlin · 11 lines
kotlin
class Button(var onClick: (() -> Unit)? = null) {
    fun click() {
        // onClick() would NOT compile (nullable, and var can't smart-cast)
        onClick?.invoke()
    }
}

val b = Button { println("clicked") }
b.click() // prints "clicked"
b.onClick = null
b.click() // does nothing, no NPE

go deeper

for a junior

Knows to use onClick?.invoke() and that direct onClick() won't compile on a nullable type.

for a middle

Explains f() desugars to .invoke(), the Unit? result type, and offers let/elvis alternatives.

for a senior

Articulates the smart-cast limitation on mutable member vars and the thread-safety reasoning behind it.

for a principal

Discusses API design — when to model an absent callback as nullable vs a no-op default lambda, and the ergonomics/perf trade-offs.

## What a nullable function type is A **function type** describes a value you can call, e.g. `() -> Unit`, `(Int) -> Int`, `(String, Int) -> Boolean`. Adding `?` makes it **nullable**: `(() -> Unit)?` means "either a function taking no args returning Unit, OR null". The parentheses around the function type are required so the `?` applies to the whole function type and not just the return type — `() -> Unit?` is a function returning a nullable `Unit`, which is different. ## Why `onClick()` does not compile In Kotlin, the call syntax `f(args)` is sugar for `f.invoke(args)`. `invoke` is an **operator function** defined on (non-null) function types. On a nullable receiver you cannot call a member/operator directly, so `onClick()` is a compile error: *"Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead."* ## Safe ways to call it ```kotlin val onClick: (() -> Unit)? = null // 1. Safe call on invoke — most idiomatic onClick?.invoke() // 2. let onClick?.let { it() } // 3. Null check + smart cast (only val / local, not var properties) if (onClick != null) onClick() // 4. Elvis with a no-op default, then call (onClick ?: {})() ``` - `onClick?.invoke()` evaluates to `Unit?`: `Unit` if called, `null` if skipped. - The smart-cast form `if (onClick != null) onClick()` works only when the compiler can prove the value will not change between the check and the call — true for a local `val` or an immutable property, but **not** for a mutable `var` member property (it could be changed by another thread), where you must use `?.invoke()` or copy into a local first. ## Passing arguments For `val transform: ((Int) -> Int)?`, you pass args inside invoke: `transform?.invoke(5)`.

  • Why does `if (onClick != null) onClick()` fail to compile when `onClick` is a `var` member property?
    Smart casts require the value can't change after the check; a mutable member `var` could be reassigned (e.g. by another thread) between check and call, so the compiler refuses to smart-cast it to non-null. Copy to a local val or use `?.invoke()`.
  • What is the type of the expression `onClick?.invoke()`?
    `Unit?` — it is `Unit` when the function ran and `null` when the receiver was null, because `?.` makes the whole expression nullable.

Like dialing a phone number that might be empty — ?.invoke() checks there's a number before dialing.

saying these in an interview costs you the question

  • Claiming `onClick()` works directly on a nullable function type
  • Saying `?.invoke()` throws if null (it does not — it skips)
  • Forgetting the parentheses distinction `(() -> Unit)?` vs `() -> Unit?`
  • Insisting smart cast works on a mutable `var` member property
  • Calling `onClick!!.invoke()` as the normal/safe approach

context