extension function — a Kotlin mechanism that allows adding new methods to existing classes without inheritance and without modifying the source code. The function is declared with a prefix in the form of a receiver type and is called as a regular method of that type. According to Kotlin Documentation, 2025, extension functions are compiled into static methods with the receiver as the first parameter, which guarantees zero overhead at runtime compared to regular calls.
Key Takeaways
An extension function is a function declared outside a class but called as its method. It takes a receiver type to which the new method is added. Inside the function, the keyword this refers to the instance of the receiver type. This approach allows extending final classes, third-party library classes, and even primitive types without creating wrappers.
Unlike Java, where adding a method to an existing class requires either inheritance or a static utility class, Kotlin offers an elegant solution without boilerplate. According to a Kotlin Foundation study (2024), extension functions are among the top five most used language features — 78% of Kotlin developers use them in their daily work.
The Kotlin compiler translates an extension function into a static method where the receiver instance is passed as the first argument. This means extension functions do not modify the target class's bytecode and do not break encapsulation — only public fields and methods of the receiver are accessible.
Use extension functions to write clean utility functions that are logically tied to a specific type but cannot be added to its source code.
The basic syntax of an extension function: the receiver type name, a dot, the function name, then parameters and return type. Inside the function, the receiver is accessible via this.
// Extension function for String
fun String.isEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
// Called as regular String method
val result = "user@example.com".isEmail() // true
In this example, isEmail() becomes available for all strings. Inside the function, this refers to the string itself on which the method is called. Kotlin allows omitting this in most cases — you can just write contains("@") instead of this.contains("@").
Extension functions can accept additional parameters and return values of any type. This makes them full-fledged functions, not just syntactic sugar.
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
if (this.isEmpty()) return listOf(default)
return this
}
val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)
The default parameter defines the value returned for an empty list. An extension function can be generic — the List
Extension functions in Kotlin use static dispatch, not virtual dispatch. This is a key difference from regular class methods. Which extension function is called is determined at compile time by the variable's static type, not by its actual runtime type.
open class Animal
class Dog : Animal()
fun Animal.speak() = "Animal sound"
fun Dog.speak() = "Woof"
fun test() {
val animal: Animal = Dog()
println(animal.speak()) // "Animal sound" — static type Animal
val dog: Dog = Dog()
println(dog.speak()) // "Woof" — static type Dog
}
Even though the animal variable points to an instance of Dog, the extension function for Animal is called because the variable's static type is Animal. If speak() were a virtual class method, Dog.speak() would have been called. This behavior is important to consider when designing APIs with extension functions.
Extension functions are not class members — they are regular functions that require an import to be used in another file. Kotlin provides two import options: by function name or with renaming.
// Import by name
import com.example.extensions.isEmail
// Import with alias (to resolve conflicts)
import com.example.extensions.isEmail as isValidEmail
// Call after import
val valid = "test@test.com".isEmail()
Renaming via as is useful for name conflicts — for example, if two libraries provide an extension function with the same name for the same type. In this case, you can import one of them with a different name and call it using the new name.
Extension functions can be declared at different levels:
| Level | Visibility | Example |
|---|---|---|
| Top-level | Entire project after import | fun String.isEmail() |
| Member extension | Inside the owner class | class A { fun B.ext() } |
| Local | Inside a function | fun test() { fun String.ext() } |
Member extension functions are a special case where an extension function is declared inside another class. In this case, both the receiver (this of the function) and the members of the outer class are accessible inside the extension function.
If a class has a method with the same signature as an extension function, the class member is always called. An extension function never overrides a class method — this is an architectural decision to prevent accidental overrides.
class User {
fun greet() = "Hello from class"
}
fun User.greet() = "Hello from extension"
fun main() {
val user = User()
println(user.greet()) // "Hello from class"
}
Even if the extension function is defined later and has the same signature, the compiler will choose the class method. The Kotlin compiler issues a warning when it encounters such a situation. The only way to call the extension function when a class method exists is to invoke it as a regular function: greet(user).
In Android development, extension functions have become a standard tool for working with View, Context, and fragments. The Android KTX library is built on extension functions, providing convenient wrappers over the Android API.
// Extension function for working with View
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
// Extension function for Context — toast
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
// Extension function as DSL builder
fun String.colorFormat(color: String): String =
"$color$this\u001b[0m"
The toast() extension function makes Context-dependent calls concise: instead of writing Toast.makeText(context, message, length).show(), you can simply write context.toast("Text"). This reduces boilerplate and makes code more readable.
Frequently Asked Questions
No, extension functions use static dispatch. If you declare an extension function for a base class and the same one for a subclass, which one is called is determined by the variable's static type at compile time, not by the actual runtime type.
You can declare extension functions for nullable receiver types: fun String?.isNullOrEmail(). Inside such a function, this can be null, so you must use safe call operators or explicit null checks.
No, extension functions are compiled into static methods. At the bytecode level, calling an extension function is identical to calling a static method with the receiver as the first parameter. No reflection or dynamic dispatch is involved.
Yes, extension properties work similarly to extension functions but cannot hold state — only a getter and a setter. For example: val List
In Java you write StringUtils.isEmail(str), while in Kotlin you write str.isEmail(). The difference is not just syntax: extension functions support IDE autocompletion, improve readability of call chains, and allow the IDE to suggest relevant functions for a specific type. Java does not have these capabilities.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also