---
title: 'Extension Functions'
source: 'https://academia.sh/en/courses/kotlin/extension-functions'
course: 'The Kotlin Language'
language: en
updated: '2026-10-21T05:02:40+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Writes an extension function and an extension property for an existing type'
    - 'Writes an extension on a nullable receiver and calls it with a null value'
    - 'Measures that the extension is never chosen while a member that fits the call exists'
    - 'Separates the extension, which is chosen by the static type, from the member, which is chosen by the object'
---

# Extension Functions

Writes an extension function, an extension property, and an extension on a nullable receiver, and measures that an extension cannot shadow a member and is chosen by the static type.

In the previous lesson a class handed its behavior to another object with `by`, and we saw that delegation does not forward `equals`. In this lesson the behavior is added to the class from the outside: you can give a new function to a type whose source you cannot touch. The question is whether this addition can take the place of a member of the type, and in particular whether it can change a member such as `equals`.

An **extension function** is a function written in front of the name of an existing type and called on an instance of that type: `fun String.wrapped(...)`. The definition of the type is not touched; the extension only makes the call syntax look like the type's own. Inside the body, `this` points to the object the function was called on, that is, the receiver.

This `this` should not be confused with the `this` and `it` distinction we saw in the Scope Functions lesson: there the question was which object is visible, while here `this` is only the receiver of the extension and the measurement is separate from that question. The measure of this lesson is the same as everywhere in this course: what makes these two values equal?

## The Measurement Setup

`Criterion` stays in the first block, in the same form, in every lesson. Each observation carries three values: `first`, `twin` (a value built separately with the same recipe), and `other` (a value that differs in only one respect). The rule sees the result, not the reason.

```kotlin
// Criterion.kt — classifies the criterion that makes two values equal
class Criterion(val label: String, first: Any?, twin: Any?, other: Any?) {
    val sameObject = first === twin
    val equalTwin = first == twin
    val equalOther = first == other

    val name: String = when {
        sameObject -> "singleton"
        !equalTwin -> "identity"
        !equalOther -> "content"
        else -> "partial-content"
    }

    fun report() {
        println("$label: same-object=$sameObject, twin=$equalTwin, other=$equalOther -> $name")
    }
}

fun summarize(checks: List<Criterion>) {
    for (name in listOf("singleton", "identity", "content", "partial-content")) {
        println("$name: ${checks.count { it.name == name }}")
    }
}
```

**TN30.** All the extensions were written in `Main.kt`, next to `main`; an extension defined in a separate file and imported was not measured in this lesson.

## Extension Function and Property

The block below writes three extensions. `wrapped` adds a mark to both sides of a string. `lastWord` is an extension **property**: its value is not stored, it is computed with `get()` on every read. The third one has `String?` as its receiver; it can be called on a null value too, and inside the body `this` is a nullable type.

```kotlin
// Main.kt — extension function, extension property, and nullable receiver
fun String.wrapped(mark: String): String = mark + this + mark

val String.lastWord: String
    get() = this.substringAfterLast(' ')

fun String?.orPlaceholder(): String = this ?: "none"

fun main() {
    println("ada".wrapped("*"))
    println("one two three".lastWord)

    val present: String? = "here"
    val missing: String? = null
    println(present.orPlaceholder())
    println(missing.orPlaceholder())
}
```

```
*ada*
three
here
none
```

`wrapped` and `lastWord` were written on a `String` as if they were members of the type. `orPlaceholder` did not blow up on the null `missing`: an ordinary member call would not work on null, but because an extension takes the receiver like a parameter, null is a valid receiver too. For this reason an extension with a `String?` receiver can handle null without a safe call (`?.`).

An extension property cannot hold a field: it cannot be given a stored value. We checked this first: the fragment below did not compile in Kotlin 2.4.21, which is why it stays without output.

```kotlin
val String.cache: Int = 0
```

**TN31.** The reason for the rejection, that an extension property does not carry a field, is taken from the definition of the language and not from the error text of the compiler; we do not write the error text in the lesson.

## An Extension Cannot Shadow a Member

What happens if you write an extension with the same name as a member of a type, but with a narrower parameter type? The block below adds an `equals(other: Plain)` extension to the `Plain` class, next to the `equals(other: Any?)` member that comes from `Any`. The extension compares the `id`s of two `Plain` values. We call it in three ways: a direct call, `==`, and a call through a variable of type `Any`.

```kotlin
// Main.kt — an equals extension does not shadow the member
class Plain(val id: Int)

fun Plain.equals(other: Plain): Boolean = this.id == other.id

fun main() {
    val a = Plain(1)
    val b = Plain(1)
    val asAny: Any = b

    println(a.equals(b))
    println(a == b)
    println(a.equals(asAny))
    Criterion("equals-extension", Plain(1), Plain(1), Plain(2)).report()
}
```

```
false
false
false
equals-extension: same-object=false, twin=false, other=false -> identity
```

All three calls gave the same result: the comparison came out false even though the `id`s are equal. The extension was written, but no call went to it: the member takes `Any?`, so a `Plain` argument fits it too, and when a member fits the call the extension is not considered, even though its parameter is more specific. The extension has a different signature; it does not shadow the member and only stands as an overload. `a == b` always resolves to the `equals(Any?)` member and never calls the extension. This is why the criterion is `identity`: the two `Plain` values carry the same `id`, but the equality rule does not know it.

**TN32.** We do not write the claim that the compiler gives a warning when an extension collides with a member; the compiler warning is not checked in this lesson. Even if an extension is written with a parameter type different from the member, the member is chosen when the argument fits it; the extension gets its turn only when the member cannot be applied. This case is measured in the next section with `Box.scale`.

## Same Name, Different Parameter

In the previous section we looked at a call whose argument fits both the member and the extension, and saw that the member wins. What if the argument does not fit the member? The member takes an `Int`, the extension takes a `Double`, and the type of the argument decides which one the call goes to. The block below measures this on the `Box` class. It also adds an extension marked `infix` and a `toString` call on a nullable receiver.

```kotlin
// Main.kt — extension with a different parameter is chosen, infix extension, toString on a null receiver
class Box(val size: Int) {
    fun scale(factor: Int): Int = size * factor
}

fun Box.scale(factor: Double): Double = size * factor

infix fun Box.bigger(other: Box): Boolean = size > other.size

fun main() {
    val box = Box(3)
    println(box.scale(2))
    println(box.scale(2.5))
    println(box bigger Box(2))

    val nothing: Any? = null
    println(nothing.toString())
    println(nothing?.toString())
    Criterion("box", Box(1), Box(1), Box(2)).report()
}
```

```
6
7.5
true
null
null
box: same-object=false, twin=false, other=false -> identity
```

`box.scale(2)` went to the member and gave `6`; `box.scale(2.5)` did not fit the `Int` parameter of the member, so it went to the extension and gave `7.5`. The extension lost while the member fitted the call and worked when it covered an argument type the member did not. This draws the limit of "an extension cannot shadow a member": if the member applies, the extension is not considered.

The expression `box bigger Box(2)` gave `true`. The `infix` mark lets you write an extension like a binary operator, without the dot and the parentheses. This is only a convenience of writing.

The lines on the nullable receiver are interesting. The variable `nothing` has the type `Any?` and the value null; `nothing.toString()` printed `null` without blowing up. The reason is that the standard library has a `toString` extension with an `Any?` receiver; it is called as if it were a member, but it handles null. `nothing?.toString()` also printed `null`, but this time because of the safe call: the call was never made and the value of the expression became null. The two lines gave the same output by different routes; in one the function ran, in the other it did not.

The `Criterion` line is again `identity`: `Box` is an ordinary class, and the extensions did not touch equality.

**TN33.** The `Box.scale` member takes an `Int` and the extension takes a `Double`; their signatures differ. It was measured that the call `box.scale(2)` goes to the member; since the extension expects a `Double`, it is not a candidate for an `Int` argument in the first place. The `Int` argument fits the member, so the member was chosen.

## Generic Receiver and Extension Inside a Class

An extension shows two more odd behaviors. The receiver type can be generic: an extension written on `List<T>` works with every element type. And an extension can be declared inside a class; then it can be called only from inside that class, and it can reach the members of the class too. The block below measures the two together.

```kotlin
// Main.kt — extension with a generic receiver and an extension declared inside a class
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null

class Formatter(private val mark: String) {
    fun String.marked(): String = mark + this + mark

    fun run(text: String): String = text.marked()
}

fun main() {
    println(listOf(1, 2, 3).secondOrNull())
    println(listOf("a").secondOrNull())
    println(emptyList<Int>().secondOrNull())
    println(Formatter("|").run("ada"))
    Criterion("formatter", Formatter("|"), Formatter("|"), Formatter("#")).report()
}
```

```
2
null
null
|ada|
formatter: same-object=false, twin=false, other=false -> identity
```

`secondOrNull` worked on three lists: `2` on the list of three `Int` values, `null` on the one-element `String` list, and `null` on the empty list. Although a single extension was written for different element types, every call inferred its own type; because the return type is `T?`, null was returned safely in the empty case.

The `marked` extension inside `Formatter` read the `mark` property of the class: `Formatter("|").run("ada")` gave `|ada|`. In the extensions above, the receiver was a single `this`; here there are two receivers, the `Formatter` instance and the `String` receiver. `marked` is visible only inside the body of `Formatter`; outside the class, `"ada".marked()` cannot be written.

The criterion is again `identity`: even though two `Formatter` values carry the same mark, they are not equal. That extensions do not touch the equality of the type, whether they are written outside or inside a class, was repeated in the third measurement of this lesson.

**TN34.** That a call to `marked` from outside the class does not compile was not measured separately; the scope information is taken from the definition of the language.

## The Selection Rule from the Measurements

An extension function is added to a type from the outside, but the way the call is written looks like a member call. This similarity makes the question of which one is chosen necessary. The measurements of this lesson gave three rules. The first: if the argument fits the member, the member wins; the `equals` member takes `Any?`, so the `Plain` argument fitted it, and the `Plain.equals` extension was chosen in none of the three calls, even though its parameter is more specific. The second: if the member cannot be applied, the extension gets its turn; the `Box.scale` member expects an `Int`, so `scale(2.5)` did not fit it and went to the extension. The third: the choice is made by the static type of the variable; the extension does not look at the class at run time.

A common mistake is to take an extension as a way of changing a member. A person who writes an extension with the same name to "fix" a member of a type sees that the code keeps working and nothing changes, because the extension is never called as long as the member fits the call. If the behavior of the member has to change, the type itself changes or a new name is chosen. The same result was seen for `equals` in this measurement, and the criterion stayed `identity`.

The nullable receiver detail shows a second trap. `nothing.toString()` and `nothing?.toString()` printed the same output, but in the first the function ran and in the second it did not. You cannot tell from the output which one was used; the difference shows when the function has a side effect. A person who writes an extension with a nullable receiver must know that this function will also run on a null receiver, and must write its body accordingly.

The generic-receiver extension and the extension inside a class bring up the question of scope. `secondOrNull` was written once and worked on three different lists; `marked` was visible only inside the body of `Formatter`. The first is a function outside the type that can be called from anywhere; the second is a function that reaches the members of the class but can be called only from inside that class. Which one you choose depends on whether the function needs the state of the class: if it does, it is written inside the class, and if it does not, outside.

## Predicting Before Measuring

Every measurement in this lesson fits the same pattern: write a call, say which one will run, then look at the output. If you can say the member for `box.scale(2)` and the extension for `box.scale(2.5)`, you have understood the selection rule: the member if the argument fits the parameter of the member, the extension if it does not. The same logic holds for `bigger`; since `Box` has no member named `bigger`, the extension runs, and the `infix` mark only changes the way it is written.

On the null receiver, the prediction has two steps. A person who says "it blows up" for `nothing.toString()` has assumed that `toString` is a member and cannot be called on null. The measurement printed `null`, because the call went not to a member but to an extension that handles a null receiver. This means that the way an expression is written (`nothing.toString()`) does not tell which function it calls; you have to look at the type of the receiver and at the visible extensions.

With the generic-receiver extension, the prediction is about the return value. On a list of three elements `2` is expected, and on a one-element or empty list `null`; the `T?` return type covers these two cases with the same value. On the identity lines, `Box` and `Formatter` are ordinary classes, so how many extensions are written does not affect the result.

Equal output and equal route are two separate facts: the two ways of writing `nothing` gave the same output but did not take the same route, and a person who looks only at the output does not see the second. It is a good habit to write next to each line the route by which that output was reached.

## The Bounding Measurement

If an extension cannot shadow a member, by which rule is an extension chosen? Members are chosen by the run-time class of the object. Extensions are chosen by the declared type of the variable, that is, by the static type. **Static dispatch** means that the function to be called is chosen at compile time by looking at the declared type of the variable; **dynamic dispatch** means that it is chosen by looking at the run-time class of the object. Extensions use the first.

The block below holds the same object with two different static types. One `label` extension is written for each of `Speaker` and `Loud`, and `speak` is a member of the class, which `Loud` overrides. In M08/K02 we looked at which type makes the decision; this measurement does not return to that question, and the lines below only put these two selection rules side by side.

```kotlin
// Main.kt — extension is chosen by the static type, member by the object
open class Speaker {
    open fun speak(): String = "base"
}

class Loud : Speaker() {
    override fun speak(): String = "loud"
}

fun Speaker.label(): String = "label-base"

fun Loud.label(): String = "label-loud"

fun main() {
    val exact = Loud()
    val asSpeaker: Speaker = exact

    println(exact.label())
    println(asSpeaker.label())
    println(exact.speak())
    println(asSpeaker.speak())
    Criterion("two-static-types", asSpeaker, exact, Speaker()).report()
}
```

```
label-loud
label-base
loud
loud
two-static-types: same-object=true, twin=true, other=false -> singleton
```

`asSpeaker` and `exact` hold the same object: the last line printed `singleton`. Even so, `label` gave two different results, because an extension looks at the type of the variable: the `Loud` extension was chosen for `exact` and the `Speaker` extension for `asSpeaker`. `speak` gave the same result on both variables, because a member looks at the real class of the object. Equality is a member too, and for this reason an extension cannot change it: `==` goes to the `equals` of the run-time object, and an extension cannot be chosen.

## Summary

- An extension function adds a new function to a type without touching its source; in the body, `this` is the receiver.
- An extension property cannot hold a field and is computed on every read; an extension property with a stored value does not compile.
- An extension whose receiver is a nullable type can be called on null and handles null itself.
- If a member fits the call, the member wins, even when the extension's parameter is more specific; since `==` always resolves to the `equals(Any?)` member, an extension cannot change `equals`.
- An extension is chosen by the static type of the variable, a member by the run-time class of the object.
- The criterion of this lesson: **identity**. Even if an extension writes `equals`, the member wins, so equality falls to identity unless the class redefines its own `equals`.

## Next Step

In this lesson we added a function to the outside of a type and measured that what is added is chosen not by the type but by the type of the variable. The next lesson makes the type itself a parameter: generics let you write a class or a function so that it works with more than one type, and variance tells whether these types can be assigned to each other.
