How would you implement a type-safe Kotlin DSL for a UI Form?

Tests Kotlin DSL scoping with lambda receivers and extensions. Strong answers sketch a Form builder using initTag, explain T.() -> Unit implicit this for child elements, and add DslMarker to block scope leaks. Red flag: describing it as simple method chaining.
WHAT THIS TESTS: The interviewer wants to see if you understand how Kotlin transforms function literals with receiver into hierarchical type-safe builders. They care about scope control, implicit receiver management, and the difference between a fluent API and a true DSL. Senior candidates should demonstrate why extension functions on the receiver enable discoverability and how DslMarker annotations prevent invalid nesting at compile time.
A GOOD ANSWER COVERS: First, define a Form class that acts as the root receiver and expose child factory methods like textField and button as extensions or members on Form. Second, explain that each factory takes a lambda with receiver, for example TextField.() -> Unit, so inside the block the implicit this is the new child instance, allowing direct property assignment. Third, describe a generic initTag helper that instantiates the child, executes its init lambda, and attaches it to the parent list, which keeps the code DRY across all node types. Fourth, mention adding a DslMarker annotation to the base Tag class so the compiler rejects attempts to call a Button method from inside a TextField block, which enforces the tree structure.
COMMON WRONG ANSWERS: Saying you would use a standard Builder class with chained setters misses the entire point of receiver scoping. Proposing plain higher-order functions without a receiver type loses the implicit this that makes the syntax declarative. Forgetting DslMarker or claiming scope leaks are acceptable at runtime shows a lack of production DSL experience. Another red flag is suggesting reflection or code generation when the language provides first-class builder support.
LIKELY FOLLOW-UPS: How would you support optional attributes or conditional blocks without breaking the DSL syntax? Can you make the DSL extensible so consumers add custom components without modifying the Form class? How do you handle rendering or diffing the tree after construction? What are the tradeoffs between inline reified helpers and explicit tag classes for performance?
ONE CONCRETE EXAMPLE: Imagine a Form builder where form is a top-level function returning Form. Inside, textField takes TextField.() -> Unit. The TextField class has var label and var hint. The form function creates Form, calls its lambda, and inside that lambda textField is available because it is a member of Form. The textField helper uses initTag with TextField and init which runs the lambda with TextField as receiver, so the caller writes label equals Email directly. A DslMarker annotation on the base Component class ensures the compiler errors if someone accidentally calls button inside textField.
Source: kotlinlang.org
Read the original → kotlinlang.org
- #kotlin
- #dsl
- #type-safe builders
- #android
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.