Skip to content
AgileLogics
5 min read

Extension Functions

Prerequisites: Functions, Classes and objects

An extension function adds a new function to an existing type — even one you didn't write, such as String or Int — without changing its source code or inheriting from it.

Syntax

fun ReceiverType.functionName(params): ReturnType { ... }

Example

fun String.initials(): String =
    split(" ").filter { it.isNotEmpty() }.joinToString("") { it.first().uppercase() }

fun Int.isEven() = this % 2 == 0

val String.wordCount: Int
    get() = trim().split(Regex("\\s+")).count { it.isNotEmpty() }

fun main() {
    println("grace brewster hopper".initials())
    println(4.isEven())
    println("  the quick brown fox ".wordCount)
}

Expected output

GBH
true
4
  • Inside an extension, this is the value it was called on (the receiver), and its members can be used directly — split(...) means this.split(...).
  • Extension properties (wordCount) work the same way, but they can't store data — they must have a getter.

Much of Kotlin's standard library is built this way: map, filter, and trim are extension functions.

Nullable receivers

An extension can be called on a nullable value:

fun String?.orPlaceholder() = if (this.isNullOrBlank()) "—" else this

fun main() {
    val missing: String? = null
    println(missing.orPlaceholder())
    println("Ada".orPlaceholder())
}
—
Ada

What extensions can't do

  • They can't access a class's private members.
  • They're resolved at compile time from the variable's declared type — they don't override anything, and they don't change the class.

Common mistakes

  • Adding very general names to common types (fun String.process()). Keep extension names specific, and keep widely-used extensions in a clearly named file, so readers can find them.