Kotlin Extension Functions
An extension function adds a new function to an existing class without modifying the class source code. You write it outside the class, but it looks and behaves like a regular member of that class. This is one of the most powerful and practical features of Kotlin.
The Problem Extension Functions Solve
Imagine you want a function that checks whether a String is a valid email address. The String class does not have this built in. Without extension functions, you would write a utility function and call it like this:
// Utility style — awkward
fun isValidEmail(email: String): Boolean {
return email.contains("@") && email.contains(".")
}
val email = "user@estudy247.com"
println(isValidEmail(email)) // true
With an extension function, the same logic feels like it belongs to String:
// Extension function — natural and readable
fun String.isValidEmail(): Boolean {
return this.contains("@") && this.contains(".")
}
val email = "user@estudy247.com"
println(email.isValidEmail()) // true
Syntax of an Extension Function
fun ReceiverType.functionName(parameters): ReturnType {
// 'this' refers to the receiver (the object the function is called on)
}
fun String.shout(): String {
return this.uppercase() + "!!!"
}
println("hello".shout()) // HELLO!!!
println("kotlin".shout()) // KOTLIN!!!
Diagram — How Extension Functions Work
"hello".shout()
│ │
│ └─ Extension function defined on String
└─ Receiver: the String instance ("hello")
Inside the function, 'this' = "hello"
Output: "HELLO!!!"
Practical Extension Function Examples
Int Extensions
fun Int.isEven(): Boolean = this % 2 == 0
fun Int.square(): Int = this * this
fun Int.isPrime(): Boolean {
if (this < 2) return false
for (i in 2..this / 2) if (this % i == 0) return false
return true
}
println(6.isEven()) // true
println(7.square()) // 49
println(13.isPrime()) // true
String Extensions
fun String.toTitleCase(): String {
return this.split(" ").joinToString(" ") { word ->
word.lowercase().replaceFirstChar { it.uppercase() }
}
}
fun String.removeSpaces(): String = this.replace(" ", "")
println("the quick brown fox".toTitleCase()) // The Quick Brown Fox
println(" k o t l i n ".removeSpaces()) // kotlin
List Extensions
fun List.secondOrNull(): Int? = if (this.size >= 2) this[1] else null fun List .printAll() { this.forEachIndexed { i, item -> println("${i + 1}. $item") } } val nums = listOf(10, 20, 30) println(nums.secondOrNull()) // 20 val courses = listOf("Kotlin", "Android", "Jetpack") courses.printAll() // 1. Kotlin // 2. Android // 3. Jetpack
Extension Properties
You can also add properties to existing classes using extension properties. These cannot store state — they compute their value each time.
val String.wordCount: Int
get() = this.trim().split("\\s+".toRegex()).size
val Int.isNegative: Boolean
get() = this < 0
println("Hello Kotlin World".wordCount) // 3
println((-5).isNegative) // true
Extension Functions on Nullable Types
fun String?.orUnknown(): String {
return this ?: "Unknown"
}
val name: String? = null
println(name.orUnknown()) // Unknown
val city: String? = "Delhi"
println(city.orUnknown()) // Delhi
Extensions vs Member Functions
class Greeter {
fun greet() = println("Member greet!") // member function
}
fun Greeter.greet() = println("Extension greet!") // extension function
Greeter().greet() // Member greet! — member wins when names conflict
When a member function and an extension function have the same name, the member function always takes priority.
When to Use Extension Functions
Scenario | Use Extension Function? -------------------------------------------------|------------------------ Add helper to a class you wrote | Maybe (consider member fn) Add helper to a library class (String, List) | Yes Add helper to a class from a 3rd-party library | Yes (cannot modify source) Group related utilities without a Util class | Yes
