skip to content

What is a function type with receiver like A.() -> Unit, and how does it differ from a plain (A) -> Unit?

level: juniorimportance: must knowfreq 70%

answer

  1. Receiver becomes this, members unqualified
  2. Call as a.f() OR f(a)
  3. (A)->Unit uses it; A.()->Unit uses this
  4. apply/with use receivers; let/also use it
  5. Foundation of builder DSLs

basics

~10 s

It is a lambda that runs as if it lives inside an object of type A. Inside the block, you can call A's methods directly without naming the object, because A becomes 'this'.

solid answer

~40 s

A.() -> Unit is a function type with receiver: when you invoke such a lambda you must supply an instance of A, and inside the body that instance becomes the implicit receiver this. So members of A can be called unqualified. A plain (A) -> Unit takes A as an ordinary parameter you reference by name (e.g. it). Syntactically: with `val f: A.() -> Unit`, you call it as `a.f()` or `f(a)`, and the body sees `this == a`. With `val g: (A) -> Unit` you call `g(a)` and refer to the value as `it`. Receiver types power builder DSLs (e.g. `html { body { ... } }`) and stdlib functions like apply and with. The lambda is compiled to a function whose first 'extension' parameter is the receiver.

code

kotlin · 10 lines
kotlin
fun StringBuilder.demo(block: StringBuilder.() -> Unit) = block()

val s = StringBuilder().apply {       // apply: T.() -> Unit
    append("Hello")                   // this.append
    append(", world")
}
println(s)                            // Hello, world

val parameterStyle: (StringBuilder) -> Unit = { sb -> sb.append("!") }
val receiverStyle: StringBuilder.() -> Unit = { append("!") }

go deeper

for a junior

Knows the body runs with A as this and members are called unqualified.

for a middle

Distinguishes this vs it, knows apply/with vs let/also, and both call forms.

for a senior

Explains the extension-receiver compilation and why DSLs rely on it.

for a principal

Frames receiver lambdas as the language primitive behind type-safe builders and discusses API design trade-offs of receiver vs parameter lambdas.

## What it is A **function type with receiver** has the shape `Receiver.() -> ReturnType`. The part before `.()` is the **receiver type**. When you call a value of this type, you provide an instance of the receiver, and inside the lambda body that instance becomes the **implicit receiver** — referenced as `this` and usable to call members **unqualified**. ```kotlin val greet: String.() -> Unit = { println("len=$length, upper=${uppercase()}") } // length and uppercase() are String members, called with no qualifier "hi".greet() // call style 1: receiver before the dot greet("hi") // call style 2: receiver as first argument ``` Both call styles are legal for a receiver type. Inside the body, `this` is the `String`, so `length` means `this.length`. ## Versus a plain parameter lambda - `(A) -> Unit` — A is an **ordinary parameter**. You access it by name, or as the implicit single-parameter name `it`. There is no implicit `this` of type A. - `A.() -> Unit` — A is the **receiver**. It becomes `this`; there is no `it` for it. Members are called unqualified. ```kotlin val p: (StringBuilder) -> Unit = { it.append("x") } // must write it. val r: StringBuilder.() -> Unit = { append("x") } // append() is this.append() ``` ## Why it exists Receiver lambdas are the foundation of **type-safe builder DSLs**. A function like `fun html(block: HTML.() -> Unit)` lets callers write `html { head { ... }; body { ... } }`, calling `head`/`body` (members of `HTML`) directly inside `{ }`. The stdlib scope functions `apply` and `with` use receiver lambdas (`T.() -> Unit`, `T.() -> R`), while `let` and `also` use plain `(T) -> R` parameter lambdas (so they use `it`). ## Under the hood The compiler turns `A.() -> Unit` into a function value whose receiver is passed like an **extension-function receiver** (effectively a hidden first argument). That is why the two call forms `a.f()` and `f(a)` are interchangeable, and why an extension function reference `A::member` (and an extension lambda) can satisfy this type. ## Key keywords/APIs - `this` — the implicit receiver inside the body. - `apply` (`T.() -> Unit`), `with` (`T.() -> R`) — receiver lambdas. - `let` / `also` — plain `(T) -> R` lambdas, use `it`. - Extension function declarations and references compose with these types.

  • Which stdlib scope functions use a receiver lambda and which use a parameter lambda?
    apply and with use receiver lambdas (this); let and also use parameter lambdas (it); run has a receiver-lambda form.
  • Can you call a receiver lambda f: A.() -> Unit using the syntax f(a)?
    Yes. Both a.f() and f(a) are valid; the receiver may be passed as the first argument.

Plain lambda hands you a tool you must name; a receiver lambda puts you inside the workshop so every tool is just within reach.

saying these in an interview costs you the question

  • Says the receiver is accessed via 'it' inside the body
  • Claims A.() -> Unit and (A) -> Unit are interchangeable types
  • Thinks you cannot call members unqualified inside the block
  • Confuses apply (receiver) with let (parameter)

context