In a type-safe builder like an HTML DSL, what is an "implicit receiver" and why can you call functions like body { ... } inside html { ... } without writing any object name before them?
answer
- A.() -> Unit = lambda with receiver
- Implicit receiver = the this you don't type
- Unqualified call binds to receiver's members
- Same idea as an extension function, but as a value
- Fully static — no reflection
basics
~20 sA lambda with a receiver runs as if its body were inside a special object. That object is the 'implicit receiver', so its members can be called by name alone, with no dot before them.
solid answer
~40 sA function whose last parameter has a receiver type like Html.() -> Unit invokes its lambda with an Html instance as the implicit receiver. Inside that lambda, this refers to the Html object, so calling body { } resolves to this.body { }. You don't name the receiver because the compiler binds unqualified members to it automatically. This is exactly how builder functions such as html { } expose body, head, and other members directly. The receiver type is part of the function type (A.() -> Unit), so the compiler knows which members are in scope before runtime. Builders nest these receiver lambdas so each level adds the right object as the current this.
code
kotlin · 10 linesfun html(init: Html.() -> Unit): Html = Html().apply(init)
class Html { fun body(init: Body.() -> Unit) = Body().init() }
class Body { fun p(text: String) = println(text) }
html { // this: Html
body { // this.body { } -> this: Body
p("hello") // this.p("hello")
}
}go deeper
Can state that the lambda runs against an object and that object's members are callable without a name.
Names the A.() -> Unit type and explains the builder function passes an instance as receiver.
Connects it to extension-function mechanics and emphasizes it is fully static / compile-time.
Frames it as the enabling primitive for type-safe builder DSLs and contrasts with parameter-style lambdas for API ergonomics.
## What an implicit receiver is Kotlin has a function type called a **lambda with receiver**, written `A.() -> Unit`. When you call a function that takes such a lambda, you supply an instance of `A`, and inside the lambda body that instance becomes the **implicit receiver** — accessible as `this` and, crucially, usable *without* writing `this.` before each member. Think of an ordinary extension function: `fun Html.body() { ... }` — inside it, `this` is the `Html`. A lambda with receiver is the same idea, but for a lambda value. ## Why body { } works with no object name Given a builder: ```kotlin class Html { fun body(init: Body.() -> Unit) { /* ... */ } } class Body { fun p(text: String) { /* ... */ } } fun html(init: Html.() -> Unit): Html { val h = Html() h.init() // calls the lambda WITH h as receiver return h } ``` Usage: ```kotlin html { // 'this' here is the Html body { // resolves to this.body { } (Html.body) p("hi") // 'this' here is the Body; resolves to this.p("hi") } } ``` The compiler sees the receiver **type** baked into the function signature (`Html.() -> Unit`), so it knows `body` is a member of the implicit receiver and lets you call it unqualified. There is no runtime reflection — it is fully static. ## Key vocabulary - **Receiver**: the object a function/lambda runs against (the thing `this` points to). - **Implicit receiver**: a receiver you don't have to spell out; the compiler supplies it. - **Lambda with receiver**: a lambda whose type carries that receiver, `A.() -> Unit`. This mechanism is the foundation of every type-safe builder DSL (HTML builders, Gradle Kotlin DSL, `buildString`, `apply`).
- How is a lambda with receiver different from a normal lambda that takes a parameter?A normal lambda `(A) -> Unit` gives you `it`/a named param you must reference explicitly; a receiver lambda `A.() -> Unit` makes that argument the implicit `this`, so its members are callable unqualified.
- Is the receiver resolved at compile time or runtime?At compile time. The receiver type is part of the function type, so the compiler statically knows which members are in scope; there's no runtime lookup.
Like being a guest in someone's house: you can grab a cup from 'the kitchen' without saying whose kitchen — context makes it clear.
saying these in an interview costs you the question
- Saying the receiver is found by runtime reflection
- Confusing A.() -> Unit with (A) -> Unit (passing it vs being this)
- Claiming you must always write this.body explicitly
- Thinking implicit receivers are a special HTML-only feature rather than a language mechanism