Implement a type-safe DSL in Kotlin for a UI component

Tests understanding of Kotlin's type-safe builders via lambdas with receivers. A great answer defines model classes, creates a top-level builder function taking a Receiver.() -> Unit lambda, and defines nested builder methods.
What's really being asked
This question tests your practical application of advanced Kotlin features to design a clean, type-safe API. The interviewer is evaluating your understanding of function literals with receivers (lambdas with receivers), the implicit this scope they create, and how these combine to enable Domain-Specific Languages (DSLs). It's a test of API design thinking, not just language syntax recall. A senior answer will also touch on scope control using @DslMarker.
The full answer
A strong answer walks through the implementation in four logical steps. First, define the data model: simple classes like Form, TextField, and Button that will hold the configuration. Second, create the top-level entry point: a function like fun form(init: Form.() -> Unit): Form. Explain that this function creates a Form instance, applies the init lambda to it (making the Form instance the receiver, this), and returns the result. Third, implement the nested builders: inside the Form class, add methods like fun textField(label: String, init: TextField.() -> Unit). These methods create a child component, apply its own configuration lambda, and crucially, add the child to the parent's internal list. Fourth, explain the type safety: textField can only be called inside the form lambda because the receiver is a Form instance. This is enforced by the compiler.
The mistakes people make
The most common red flag is describing a traditional Java-style builder with method chaining (e.g., Form.Builder().setLabel("...").build()). This completely misses the question's focus on Kotlin's unique DSL capabilities. Another mistake is using a regular lambda type like (Form) -> Unit, which would force the caller to use an explicit it (it.textField(...)) and ruins the clean DSL syntax. A candidate who can't explain that the clean syntax comes from the implicit this receiver inside the lambda has a surface-level understanding. Finally, a functional but incomplete answer might forget to add the created child components to the parent's collection, resulting in an empty Form.
What usually comes next
Expect questions like: "How would you prevent a textField from being defined outside a form block?" (It's a member of the Form class, so the compiler enforces this). Or, "How would you prevent a form from being nested inside another form?" (The senior answer is to create a custom annotation and mark it with @DslMarker, then apply it to the builder classes). Another common follow-up is, "What are the performance costs?" (Minimal; the creation of a few lambda objects is negligible compared to UI rendering costs).
A concrete example
// DSL usage:
val myForm = form {
textField("Username") { required = true }
button("Submit") { enabled = false }
}// Core implementation: @DslMarker annotation class FormDslMarker
interface UiComponent
@FormDslMarker
class Form : UiComponent {
val children = mutableListOf<UiComponent>()fun textField(label: String, init: TextField.() -> Unit) {
children.add(TextField(label).apply(init))
}fun button(text: String, init: Button.() -> Unit) {
children.add(Button(text).apply(init))
}
}class TextField(val label: String) : UiComponent { var required: Boolean = false }
class Button(val text: String) : UiComponent { var enabled: Boolean = true }fun form(init: Form.() -> Unit): Form = Form().apply(init)
Interview question
What is the primary benefit of using a function literal with a receiver (e.g., Form.() -> Unit) for a type-safe DSL in Kotlin?
- a.It ensures the lambda always returns Unit, preventing unintended return values from DSL blocks.
- b.It forces explicit use of the it keyword for receiver access, improving code readability.
- c.It enables method chaining on a builder object, similar to traditional fluent APIs.
- d.It allows direct invocation of receiver methods within the lambda body, leveraging an implicit this scope.Correct
Why? this is the answer
The `Receiver.() -> Unit` syntax makes the receiver object implicitly available as `this` inside the lambda, enabling direct calls to its members (e.g., `textField()`) for a clean DSL. Option B describes a regular lambda, which requires `it` and ruins the clean DSL syntax, while Option C describes a traditional builder pattern, which misses Kotlin's unique DSL capabilities.
Just read this? Test yourself on what you have been reading.
Read the original → kotlinlang.org
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on kotlin — each one lists the topics its interview covers.
See open roles