Skip to content
intermediate Phase 1 · Kotlin Foundations

Extension Functions

Extend existing classes with new functionality using extension functions and properties.

35m
2 problems
Topic Progress 0%

Defining Extension Functions

The Problem: Adding Behavior to Existing Classes

Sometimes you need to add functionality to a class you do not own. Java requires wrapper classes or utility methods. Kotlin solves this with extension functions, which let you add methods to existing classes without modifying their source code.

Syntax

An extension function is declared with a receiver type before the function name:

fun String.removeVowels(): String {
    return this.filter { it !in "aeiouAEIOU" }
}

println("Hello World".removeVowels())  // Hll Wrld

The this keyword inside the function refers to the receiver object (the String instance the function is called on). The function is called as if it were a member of the class.

How Extension Functions Are Resolved

Extension functions are resolved statically, not polymorphically. The compiler determines which extension function to call based on the declared type of the variable, not its runtime type:

open class Animal
class Dog : Animal()

fun Animal.speak() = "Generic sound"
fun Dog.speak() = "Woof!"

val animal: Animal = Dog()
println(animal.speak())  // Generic sound (not Woof!)

This is different from virtual method calls. If you need polymorphic behavior, use regular inheritance.

Extension Functions on Nullable Types

You can define extensions on nullable types to handle null cases gracefully:

fun String?.isNullOrEmpty(): Boolean {
    return this == null || this.isEmpty()
}

val name: String? = null
println(name.isNullOrEmpty())  // true
println("".isNullOrEmpty())    // true
println("Kotlin".isNullOrEmpty())  // false

This pattern is used extensively in Kotlin's standard library.

Member vs Extension Functions

If a class already has a member function with the same signature as an extension function, the member function always wins:

class Sample {
    fun foo() = "member"
}

fun Sample.foo() = "extension"

println(Sample().foo())  // member

This rule prevents ambiguity: existing APIs always take precedence over extensions.

Extension Properties

Computed Extension Properties

Extension properties add computed properties to existing classes. They do not have backing fields, so they must be defined with custom getters:

val String.wordCount: Int
    get() = split(" ").size

println("hello world".wordCount)  // 2

Extension properties are useful for derived computations that do not need storage.

Practical Examples

val String.isEmail: Boolean
    get() = contains("@") && "." in substringAfter("@")

val String.truncated: String
    get() = if (length > 20) take(20) + "..." else this

val <T> List<T>.secondOrNull: T?
    get() = if (size >= 2) this[1] else null

println("user@example.com".isEmail)       // true
println(listOf(1).secondOrNull)            // null
println(listOf(1, 2, 3).secondOrNull)      // 2

Chaining Extensions

Extension functions and properties can be chained for readable pipelines:

val result = "  Hello World  "
    .trim()
    .lowercase()
    .replace(" ", "-")
    .take(10)

println(result)  // hello-world

When to Write Extensions

  • When you need a method on a class you do not own (e.g., Android SDK classes)
  • When the method is logically related to the class but not core functionality
  • When you want to add DSL-like syntax to existing types
  • When the method would be useful across multiple call sites

Avoid extensions that change the meaning of existing methods or depend on mutable state.

Practice Problems

0 / 3 solved
String Validator Extensions

Write extension functions on String: isPalindrome() that checks if the string reads the same forwards and backwards (case-insensitive), and truncate(maxLength) that returns the first maxLength characters followed by '...' if longer.

Solution
fun String.isPalindrome(): Boolean {
    val cleaned = lowercase().filter { it.isLetterOrDigit() }
    return cleaned == cleaned.reversed()
}

fun String.truncate(maxLength: Int): String {
    return if (length > maxLength) take(maxLength) + "..." else this
}

fun main() {
    println("Racecar".isPalindrome())              // true
    println("Hello".isPalindrome())                 // false
    println("Hello World".truncate(5))             // Hello...
    println("Hi".truncate(5))                      // Hi
}
List Running Sum Extension

Write an extension function on List<Int> called runningSum that returns a new list where each element is the sum of all previous elements including itself. Example: [1, 2, 3] becomes [1, 3, 6].

Solution
fun List<Int>.runningSum(): List<Int> {
    val result = mutableListOf<Int>()
    var sum = 0
    for (element in this) {
        sum += element
        result.add(sum)
    }
    return result
}

fun main() {
    println(listOf(1, 2, 3, 4).runningSum())   // [1, 3, 6, 10]
    println(listOf(1, 1, 1, 1).runningSum())   // [1, 2, 3, 4]
}
Nullable Type Extensions

Write an extension function on List<T> that returns the list if non-null or an empty list if null. Then write an extension on Int? that returns a formatted string like 'Value: 42' or 'No value' if null.

Solution
fun <T> List<T>?.orEmptyList(): List<T> {
    return this ?: emptyList()
}

fun Int?.formatValue(): String {
    return if (this != null) "Value: $this" else "No value"
}

fun main() {
    val nullList: List<Int>? = null
    println(nullList.orEmptyList())  // []
    println(listOf(1, 2).orEmptyList())  // [1, 2]

    val nullInt: Int? = null
    println(nullInt.formatValue())   // No value
    println(42.formatValue())        // Value: 42
}

Quiz

1. How are extension functions resolved in Kotlin?

Question 1 options

2. What happens if a class has both a member function and an extension function with the same signature?

Question 2 options

3. Why can extension properties not have backing fields?

Question 3 options

4. What is the benefit of defining an extension on a nullable type like String?

Question 4 options

Flashcards

Question

What is an extension function in Kotlin?

Answer

A function declared with a receiver type that adds methods to existing classes without modifying their source. Declared as fun ClassName.methodName(). Called as if it were a member function.

Question

Are extension functions resolved statically or dynamically?

Answer

Statically at compile time. The compiler uses the declared type of the variable, not the runtime type. This differs from virtual method dispatch.

Question

Can extension properties have backing fields?

Answer

No. Extension properties do not modify the class structure. They must be defined with custom getters only. They cannot maintain state.

Question

When should you avoid writing extension functions?

Answer

When they change the meaning of existing methods, depend on mutable state, or could surprise users of the class. Also avoid when a regular utility function would be clearer.

Revision Notes

Key Takeaways

  • 1. Extension functions let you add methods to classes you do not own.
  • 2. They are resolved statically based on the declared type, not the runtime type.
  • 3. Member functions always take precedence over extension functions.
  • 4. Extension properties cannot have backing fields and must use custom getters.
  • 5. Nullable type extensions handle null checks internally for cleaner caller code.

Interview Tips

  • Explain that extension functions are compile-time constructs, not runtime polymorphism.
  • Know that member functions shadow extension functions with the same signature.
  • Be ready to discuss when to use extensions vs utility functions.
  • Understand how extensions enable DSL-like syntax in Kotlin.

Cheat Sheet

Extension Functions Cheat Sheet

Syntax:

  • fun ReceiverType.functionName() = ...
  • this refers to the receiver object

Resolution:

  • Statically resolved at compile time
  • Member functions always win over extensions
  • Based on declared type, not runtime type

Nullable Extensions:

  • fun String?.isNullOrEmpty(): Boolean
  • Handle null internally for cleaner caller code

Extension Properties:

  • Computed properties with custom getters
  • No backing fields allowed
  • Useful for derived values

Best Practices:

  • Use for adding utility methods to classes you do not own
  • Avoid if it changes existing method semantics
  • Keep extensions focused and predictable