What is a receiver function type like String.() -> Unit in Kotlin, and how does it differ from a plain function type (String) -> Unit?
answer
- A.() -> B = receiver before the dot
- Body sees this as the receiver
- Members callable unqualified
- Basis of apply / buildString DSLs
- Call as recv.lambda() or lambda(recv)
basics
~20 sA receiver function type is a lambda that runs 'inside' an object, so you can use this and call its members directly without naming it. A plain function type just takes the object as a normal parameter you must name.
solid answer
~40 sA receiver function type A.() -> B is a lambda whose body has an implicit receiver of type A: inside the body, this refers to that A and you can call its members unqualified, exactly like an extension function. A plain (A) -> B passes A as an ordinary positional argument you reference by name (e.g. it). The syntax adds the receiver type before the dot: String.() -> Unit. You invoke such a lambda either as receiver.lambda() or lambda(receiver). This is the mechanism behind scope functions like apply and with and behind type-safe builder DSLs such as buildString { }. The compiler treats the receiver lambda like an extension lambda, so member access inside is on the receiver, not the enclosing scope.
code
kotlin · 8 linesfun greet(block: StringBuilder.() -> Unit): String =
StringBuilder().apply(block).toString()
val msg = greet {
append("Hello, ") // 'this' is the StringBuilder
append("world")
}
println(msg) // Hello, worldgo deeper
Can state that A.() -> B gives an implicit this and call members unqualified, and name apply/buildString as users.
Explains the equivalence to extension functions and both invocation forms recv.lambda()/lambda(recv).
Connects it to DSL design and notes the JVM-level identity with (A) -> B (receiver = first arg).
Discusses API-design tradeoffs of exposing receiver vs plain lambdas (discoverability vs explicitness, scope pollution).
## What a receiver function type is A **function type** describes a lambda's signature. A **plain** one is written `(A) -> B`: it takes an `A` parameter and returns `B`. A **receiver** function type is written `A.() -> B`: the part before the dot, `A`, is the **receiver type**. Inside a lambda of type `A.() -> B`, `A` is an **implicit receiver**: the keyword `this` refers to it, and you can call its members and extensions **unqualified** (without writing `this.`). This is exactly how an **extension function** body works. ## Plain vs receiver — side by side ```kotlin val plain: (StringBuilder) -> Unit = { sb -> sb.append("hi") } // must name the param val withRecv: StringBuilder.() -> Unit = { append("hi") } // 'this' is the StringBuilder ``` In `withRecv`, `append` resolves on the implicit `StringBuilder` receiver. In `plain`, you must reference `sb` (or `it`) explicitly. ## How you call it A receiver lambda can be invoked two equivalent ways: ```kotlin val sb = StringBuilder() withRecv(sb) // pass receiver as first argument sb.withRecv() // call it like an extension on the receiver ``` ## Why it matters Receiver function types are the basis of: - **Scope functions**: `apply { }` and `with(x) { }` use `T.() -> R`, so inside the block `this` is the object. - **Type-safe builders / DSLs**: `buildString { append(...) }` takes a `StringBuilder.() -> Unit`, letting the block configure the builder fluently. ```kotlin val s = buildString { append("a"); append("b") } // "ab"; 'this' is a StringBuilder val p = Person().apply { name = "Ann"; age = 30 } // 'this' is the Person ``` ## Key terms - **Receiver**: the object the lambda operates on as `this`. - **Implicit receiver**: a receiver you can access without naming it. - **Extension lambda**: another name for a receiver lambda, since it behaves like an extension function.
- How do you invoke a value of type A.() -> B?Either receiver.lambda() or lambda(receiver); both pass the receiver. The first reads like an extension call.
- Is there really a difference at the JVM level?No — both compile to a Function1; the receiver is just the first parameter. The difference is purely how the Kotlin compiler binds this inside the body.
A plain lambda is handed a tool and told its name; a receiver lambda is teleported inside the tool, so it just presses the buttons.
saying these in an interview costs you the question
- Saying the receiver lambda takes no parameter at all
- Confusing it with it (it is only for single plain params)
- Claiming you must always write this. inside
- Thinking it is a different runtime type from (A) -> B