Extensions for DSL building in Kotlin - Time & Space Complexity
When we use extensions to build DSLs in Kotlin, we want to know how the program's running time changes as we add more elements.
We ask: How does the time to build or process the DSL grow when the input size grows?
Analyze the time complexity of the following code snippet.
class Html {
private val children = mutableListOf()
fun body(block: Body.() -> Unit) {
val body = Body()
body.block()
children.add(body.content)
}
}
class Body {
var content = ""
fun p(text: String) { content += "$text
" }
}
fun html(block: Html.() -> Unit): Html {
val html = Html()
html.block()
return html
}
This code builds a simple HTML DSL using extensions and lambdas to add paragraphs inside a body.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Adding paragraphs by appending strings inside the
pfunction. - How many times: Once per paragraph added; each call appends to the content string but with increasing cost due to string immutability.
Each paragraph added triggers a string append. Since strings are immutable, each append copies the current content (O(k) for the k-th append).
| Input Size (n) | Approx. Total Operations |
|---|---|
| 10 | 55 |
| 100 | 5,050 |
| 1000 | 500,500 |
Pattern observation: The total work grows quadratically with the number of paragraphs added.
Time Complexity: O(n2)
This means the time to build the DSL grows quadratically with the number of elements added.
[X] Wrong: "Using extensions for DSL building always makes the code run instantly regardless of input size."
[OK] Correct: Even with extensions, each added element requires work, so time grows with input size.
Understanding how DSL building scales helps you explain how your code handles more data gracefully and shows you think about performance in real projects.
What if we changed the string concatenation in p to use a StringBuilder? How would the time complexity change?
Practice
Solution
Step 1: Understand what extensions do
Extensions allow adding new functions to existing classes without changing their original code.Step 2: Relate to DSL building
This helps create readable DSLs by extending classes with custom functions.Final Answer:
To add new functions to existing classes without modifying their source code -> Option AQuick Check:
Extensions add functions without modifying classes [OK]
- Thinking extensions create new classes
- Believing extensions permanently change existing functions
- Confusing extensions with access control
greet for the String class in Kotlin?Solution
Step 1: Recall extension function syntax
Extension functions are declared withfun ClassName.functionName().Step 2: Match syntax to options
fun String.greet() { println("Hello") } usesfun String.greet(), which is correct syntax.Final Answer:
fun String.greet() { println("Hello") } -> Option CQuick Check:
Extension syntax = fun ClassName.functionName() [OK]
- Placing class name as parameter instead of receiver
- Using 'extension' keyword which doesn't exist
- Incorrect function return type placement
class Box(var content: String)
fun Box.describe() = "Box contains: $content"
fun main() {
val box = Box("Apples")
println(box.describe())
}Solution
Step 1: Understand the extension function
The extension functiondescribereturns a string with thecontentproperty ofBox.Step 2: Check the main function output
CreatingBox("Apples")and callingdescribe()prints "Box contains: Apples".Final Answer:
Box contains: Apples -> Option AQuick Check:
Extension uses property value correctly [OK]
- Expecting 'content' literal instead of property value
- Confusing extension with overriding toString()
- Thinking it prints memory address
class Menu {
val items = mutableListOf<String>()
}
fun Menu.addItem(name: String) {
items.add(name)
}
fun buildMenu(block: Menu.() -> Unit): Menu {
val menu = Menu()
block(menu)
return menu
}
fun main() {
val menu = buildMenu {
addItem("Home")
addItem("About")
}
println(menu.items)
}Solution
Step 1: Understand the block parameter type
The block is a lambda with receiver:Menu.() -> Unit, so it should be called asmenu.block().Step 2: Identify the call mistake
The code callsblock(menu), which treats block as a normal function, causing an error.Final Answer:
The block should be called with menu.block() instead of block(menu) -> Option BQuick Check:
Lambda with receiver called as receiver.lambda() [OK]
- Calling lambda with receiver as normal function
- Placing extension function inside class unnecessarily
- Confusing mutable and immutable lists
class Html {
val children = mutableListOf<String>()
}
fun Html.body(block: Html.() -> Unit) {
this.block()
}
fun Html.p(text: String) {
children.add("$text
")
}
fun html(block: Html.() -> Unit): Html {
val html = Html()
html.block()
return html
}
fun main() {
val page = html {
body {
p("Hello")
p("World")
}
}
println(page.children)
}Solution
Step 1: Trace the DSL calls
Thehtmlfunction creates an Html object and callsbodyextension, which callsptwice adding paragraphs tochildren.Step 2: Check the children list content
Bothpcalls add strings "Hello
" and "World
" to the children list.Final Answer:
["Hello
", "World
"] -> Option DQuick Check:
Extensions build nested structure as list of strings [OK]
- Expecting nested tags as single string
- Assuming body creates separate object
- Thinking code causes compilation error
Ready to prove it?
Pass the test and this pattern moves unknown
