They let you add functions to a type without modifying its source (syntactic sugar). A common pitfall: extensions are resolved statically by the declared type, so they don’t behave like virtual overrides.
open class Base
class Child : Base()
fun Base.say() = "base"
fun Child.say() = "child"
val x: Base = Child()
println(x.say()) // "base" (static dispatch)
Advanced answer
Deep dive
Expanding on the short answer — what usually matters in practice:
Explain the "why", not just the "what" (intuition + consequences).
Trade-offs: what you gain/lose (time, memory, complexity, risk).
Edge cases: empty inputs, large inputs, invalid inputs, concurrency.
Examples
Here’s an additional example (building on the short answer):
open class Base
class Child : Base()
fun Base.say() = "base"
fun Child.say() = "child"
val x: Base = Child()
println(x.say()) // "base" (static dispatch)
Common pitfalls
Too generic: no concrete trade-offs or examples.
Mixing average-case and worst-case (e.g., complexity).