Skip to main content

Extension functions in depth

Extensions are resolved statically using the declared receiver type.

open class Animal
class Dog : Animal()

fun Animal.label() = "Animal"
fun Dog.label() = "Dog"

val animal: Animal = Dog()
println(animal.label()) // Animal

The runtime object is Dog, but the variable is declared as Animal; extension dispatch is not virtual.

Open in Kotlin Playground

Nullable receiver​

fun String?.orUnknown(): String = this?.takeIf { it.isNotBlank() } ?: "Unknown"

The extension handles null itself, so it can be called on a nullable variable.

Member wins over extension​

If a class member and visible extension have the same applicable signature, the member is called.

Scope extensions carefully​

Extensions are best when they express natural behavior of the receiver and have visible dependencies.

✕ Hidden side effect

fun User.uploadAndTrackAndSave() { /* many hidden dependencies */ }

✓ Focused transformation

fun User.toAnalyticsProperties(): Map<String, String> = mapOf("id" to id.toString())
Interview-ready answer

Extensions improve call-site syntax but do not modify a class, access its private state, or provide virtual overriding. They are selected statically, and real members take priority.