skip to content

Function Types with Receiver A.()->B

A receiver function type A.() -> B makes the lambda body see the object as this, which is the mechanism behind apply, buildString, and every type-safe builder DSL. If you can explain this type, you can explain how Kotlin DSLs work.

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

questions

5

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

level: juniorimportance: must knowfreq 70%

answer

  1. A.() -> B = receiver before the dot
  2. Body sees this as the receiver
  3. Members callable unqualified
  4. Basis of apply / buildString DSLs
  5. Call as recv.lambda() or lambda(recv)

basics

~20 s

A 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 s

A 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 lines
kotlin
fun greet(block: StringBuilder.() -> Unit): String =
    StringBuilder().apply(block).toString()

val msg = greet {
    append("Hello, ")   // 'this' is the StringBuilder
    append("world")
}
println(msg) // Hello, world

go deeper

for a junior

Can state that A.() -> B gives an implicit this and call members unqualified, and name apply/buildString as users.

for a middle

Explains the equivalence to extension functions and both invocation forms recv.lambda()/lambda(recv).

for a senior

Connects it to DSL design and notes the JVM-level identity with (A) -> B (receiver = first arg).

for a principal

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

context

open as a page

Compare the signatures of apply, run, with, also and let. Which use a receiver function type, which use a plain function type, and how does that change the lambda body?

level: middleimportance: must knowfreq 75%

basics

~10 s

apply, run and with give you this (receiver lambdas); let and also give you it (plain lambdas). With this you call members directly; with it you must name the argument.

open as a page

Implement a small type-safe builder DSL using a receiver function type (e.g. an html { } or buildString-style builder). Show the function signature and how the lambda gives the body an implicit receiver.

level: middleimportance: should knowfreq 60%

basics

~20 s

Write a builder function that takes a lambda with the builder as its receiver (Builder.() -> Unit), create the builder, run the lambda on it, and return the result. Inside the block users call builder methods directly.

open as a page

In a nested receiver-lambda DSL, how does Kotlin resolve a call when multiple implicit receivers are in scope, and how do @DslMarker and labeled this (this@Outer) control that resolution?

level: seniorimportance: should knowfreq 45%

basics

~10 s

When nested receiver lambdas stack, Kotlin tries the innermost receiver first, then outer ones. @DslMarker blocks reaching outer receivers implicitly, and this@Label lets you target a specific one explicitly.

open as a page

At the JVM level, how is a receiver function type A.() -> B represented, and what are the API-design tradeoffs of choosing a receiver lambda over a plain parameter lambda?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

On the JVM a receiver lambda is the same as a plain one-arg function; the receiver is just the first argument. Choosing a receiver lambda makes APIs read like DSLs but can hide which object you're acting on and pollute scope.

open as a page