# Extensions

Source: https://codewiki.com/kotlin/extensions/

> - **what**: Extensions add functions or computed properties to existing types with member-call syntax, without modifying or inheriting from those types.
> - **trap**: An extension doesn't become a real class member; it resolves from the receiver's compile-time type, and a matching real member always wins.
> - **fix**: Treat extensions as scoped declarations, limit their visibility, and test resolution with different static types, `null`, and member conflicts.

## What it is and why it exists

An extension function is declared outside a type
but can be called in the form `receiver.function()`. The type to the left of the dot in
the declaration is the receiver type, and `this` in the body is the receiver object for
that call. This syntax puts the operation's primary object first without requiring access to its source.

Extensions solve API expression and organization problems, not inheritance problems.
Third-party types, standard-library types, and stable domain models can gain operations
needed by callers while their inheritance hierarchies and object layouts remain unchanged.
Collection operations such as `map()`, `filter()`, and `joinToString()` make extensive use of this form.

Kotlin also supports extension properties.
They provide property-access syntax for computed values but can't add storage to the receiver.
Property form usually fits a cheap operation that doesn't throw and stays stable while
receiver state is unchanged; use a function when the operation takes arguments, may be expensive, or has clear action semantics.

An extension doesn't modify a class or bypass encapsulation.
An extension declared outside the type can use only APIs visible from its declaration site; it
can't read the receiver's `private` or `protected` members. Nor can a receiver subclass override it like a virtual member.

You encounter extensions in domain formatting, collection operations, third-party API adapters,
nullable-value normalization, and small DSLs. A member function is usually more accurate
when behavior defines a type's core invariant, requires private state, or must support runtime polymorphism.
An extension should express a capability added from the caller's perspective, not impersonate behavior promised by the type itself.

## How it works

### The receiver changes call syntax only

`fun String.normalized(): String` declares a function whose receiver type is `String`.
Inside its body, unqualified member access and `this` both refer to the receiver; the caller
writes `input.normalized()`. This extension receiver
is still an ordinary parameter role and doesn't inject the declaration into `String`.

The receiver type may be an interface, a generic type, a function type, or a nullable type.
In `fun  List.secondOrNull(): T?`, the type parameter precedes the function name so
it is available in both the receiver and return types. With a `Customer?` receiver, `this`
may also be `null` inside the body and must be checked like any other nullable value.

Call syntax says nothing about ownership.
An extension may mutate an already mutable receiver or return a new value, but it doesn't
copy objects automatically or gain extra permissions. Review side effects from parameters,
return values, and mutability exactly as you would for an ordinary function.

### Calls resolve at compile time

Extensions use static dispatch.
The compiler selects an extension from the expression's declared type, declarations visible
in the current scope, and the argument list. The object's runtime subtype doesn't make a call
switch automatically to a different extension with a more specific receiver.

Use these boundaries when reasoning about `value.render()`:

| Condition | Result | Review focus |
| --- | --- | --- |
| The declared type has an applicable member | The member wins | A new member can change source resolution after recompilation |
| No applicable member, but an extension is visible | Resolve among applicable extensions | Check imports, the static receiver type, and arguments |
| Only the runtime subtype has another extension | It doesn't participate dynamically | Use a virtual member or interface when polymorphism is required |
| There is no unique applicable declaration | Compilation fails | Remove ambiguity with a qualified import, alias, or distinct name |

“Members win” applies to applicable calls.
When a member and extension share a name but have different parameter lists, the extension
can still be selected as an ordinary overload. A name-only search therefore can't identify
the target; compare the receiver type, parameters, and visible scope too.

Static resolution prevents a third-party extension from replacing behavior already owned by a type,
but it also creates an evolution risk. If a dependency adds a member compatible with an existing
extension, recompiled source selects the member. Public extensions need domain-specific names and
behavior tests that run during dependency upgrades.

### Extension properties have no backing fields

An extension property must provide accessors because receiver instances have no backing-field
slot reserved for it. A read-only extension has the form `val Type.name: Result get() = ...`.
A mutable extension may declare a getter and setter, but they must read and write existing
receiver state or external storage; they can't use a `field` owned by the extension.

That restriction is a useful design signal.
If a property must store new per-instance state, change the type that owns the state, use a wrapper,
or create an explicit external map with a lifetime policy. Hiding a global map behind an extension
property readily introduces leaks, races, and shared state that is hard to observe.

Property access should look cheap and stable.
Network requests, disk reads, large allocations, or parsing that may fail shouldn't masquerade
as extension properties; a function name and parameters communicate those costs and failure
boundaries more honestly.

### Scope determines the candidate set

A top-level extension belongs to its declaring package.
Callers in another package import it, either with an explicit import that narrows the source
or with `import package.longName as localName` to resolve a name conflict. Star imports widen
the candidate set, so public code benefits from explicit imports for contested names.

Extensions can also be declared in functions or classes.
A local extension serves one implementation scope. A member extension declared in a class can
be called only when that class's dispatch receiver is available.
Narrowing declaration scope is often safer than adding a generic name to an entire package.

Visibility modifiers constrain the extension declaration itself.
A file-level `private` extension is visible only in that file, while an `internal` extension is
visible in the same module. Neither modifier grants the extension access to private implementation
details of its receiver.

## Examples

### Adding a domain transformation to strings

This first extension converts a page title into a route segment.
It uses only public `String` operations and returns a new string without changing the receiver.

<!-- quick -->

```kotlin
fun String.toRouteSegment(): String =
    trim()
        .lowercase()
        .split(Regex("\\s+"))
        .filter(String::isNotEmpty)
        .joinToString("-")

fun main() {
    val topicTitle = "  Kotlin Extension Functions  "
    val settingsTitle = "Account   Settings"

    println(topicTitle.toRouteSegment())
    println(settingsTitle.toRouteSegment())
}
```

```text
kotlin-extension-functions
account-settings
```

<!-- /quick -->

Omitting `this` doesn't change the meaning: `trim()` still runs on the current string.
If this rule makes sense only in the routing layer, keep the extension in that package or file
instead of exporting a vague name such as `normalize()` to the whole project.

This simplified rule doesn't handle Unicode normalization or every URL-encoding requirement.
Its name limits the result to a route segment in this project. If product rules are more involved,
put them in tests or a dedicated value type instead of piling on implicit behavior.

### Combining a generic function and computed property

A generic extension can preserve its element type, while an extension property fits a cheap value
computed from existing state. An empty list has no last index, so the property returns `Int?`
instead of inventing a sentinel number.

```kotlin
fun <T> List<T>.secondOrNull(): T? = getOrNull(1)

val List<*>.lastIndexOrNull: Int?
    get() = if (isEmpty()) null else lastIndex

fun main() {
    val cities = listOf("Paris", "Lyon", "Nice")
    val singleCity = listOf("Paris")
    val noCities = emptyList<String>()

    println(cities.secondOrNull())
    println(singleCity.secondOrNull())
    println(cities.lastIndexOrNull)
    println(noCities.lastIndexOrNull)
}
```

```text
Lyon
null
2
null
```

`secondOrNull()` delegates boundary behavior to the standard library's `getOrNull()` rather
than repeating an index check. `lastIndexOrNull` is computed from the list's current state on
every access and uses no additional storage. A later mutation is reflected by the next read.

The receiver is `List<*>` because the property cares only about size and doesn't need the element
type. The function needs `T` so its result retains the concrete element type. Keeping type parameters
on only the operations that need them avoids meaningless generic complexity.

### Handling `null` inside a nullable receiver

A nullable receiver lets callers omit a safe call and centralizes the missing-value policy inside
the extension. This `displayNameOr()` handles both a missing object and a blank name.

```kotlin
data class Customer(
    val givenName: String,
    val familyName: String,
)

val Customer.displayName: String
    get() = "$givenName $familyName".trim()

fun Customer?.displayNameOr(fallback: String): String {
    val name = this?.displayName
    return if (name.isNullOrBlank()) fallback else name
}

fun main() {
    val customer = Customer("Ada", "Lovelace")
    val blank = Customer(" ", " ")
    val missing: Customer? = null

    println(customer.displayNameOr("Guest"))
    println(blank.displayNameOr("Guest"))
    println(missing.displayNameOr("Guest"))
}
```

```text
Ada Lovelace
Guest
Guest
```

Calling `missing.displayNameOr("Guest")` is valid because the extension's receiver type is
already `Customer?`. Inside the function, `this` remains nullable; the code uses a safe call
to obtain the property and lets `isNullOrBlank()` combine the missing and blank cases.

Writing `missing?.displayNameOr("Guest")` instead skips the whole extension when the receiver
is `null`, making the expression evaluate to `null` rather than `"Guest"`. Whether to use `?.`
is a semantic choice, not something a “nullable values always need safe calls” habit can decide.

### Observing static dispatch and member precedence

The next program shows both resolution rules.
An `Alert`-typed variable selects `Alert.kind()`, but after it enters a `Message` parameter the
call selects only `Message.kind()`. The real `channel()` member shadows the matching extension.

```kotlin
open class Message {
    fun channel(): String = "member"
}

class Alert : Message()

fun Message.kind(): String = "message"
fun Alert.kind(): String = "alert"

// This extension cannot beat Message's member with the same signature.
fun Message.channel(): String = "extension"

fun printSummary(message: Message) {
    println(message.kind())
    println(message.channel())
}

fun main() {
    val alert: Alert = Alert()

    println(alert.kind())
    printSummary(alert)
}
```

```text
alert
message
member
```

The object received by `printSummary()` is still an `Alert`, but the parameter's declared type
is `Message`, so the extension call prints `message`. If `kind()` must vary with the runtime
subclass, make it an `open` member on `Message` or part of an interface contract.

The compiler warns that `Message.channel()` is shadowed by a member.
Keeping an extension that can never be selected through ordinary member syntax misleads readers;
delete it or give it a clear name for the distinct operation it performs.

## Pitfalls

> **Pitfall:** Treating extensions as virtual members makes a base-typed parameter ignore extensions for runtime subtypes.
> The code usually compiles, and the mistake appears only in behavior for polymorphic inputs.

**Fix:** Put runtime-dispatched behavior in an overridable member on a base class or interface.
If an extension is only a static-type adapter, make that limit clear in its name and parameter type,
then test both concrete-typed and base-typed variables.

> **Pitfall:** Declaring an extension with the same signature as a member doesn't override or replace that member.
> If a later dependency version adds a matching member, recompilation can silently change the target.

**Fix:** Avoid generic member names, especially `get()`, `size()`, `parse()`, and `toString()`.
Check new members and compiler warnings during dependency upgrades, and keep behavior tests for
critical extensions.

> **Pitfall:** Publishing many generic extensions from broad packages pollutes completion and may create import ambiguity.
> Call syntax doesn't reveal the declaring package, so reviewers can also mistake an extension for a real member.

**Fix:** Use the narrowest practical visibility and place domain extensions in the package that owns
the rule. Resolve conflicts with explicit imports or aliases instead of depending on star imports
and accidental scope order.

> **Pitfall:** An extension property can't store new per-receiver state.
> Simulating a backing field with a global mutable map creates retention, race, and cleanup problems.

**Fix:** Let an extension property compute only from existing receiver state.
Real new state belongs in the original type, a wrapper, or explicit storage. When external storage
is unavoidable, expose its key strategy, threading model, and lifetime instead of hiding it in an accessor.

> **Pitfall:** Using `?.` on a nullable-receiver extension skips the body when the receiver is `null`.
> That can bypass the fallback, logging label, or normalization policy the extension was meant to own.

**Fix:** Inspect the extension's receiver type first.
If it already accepts `Type?` and defines `null` semantics, call it directly. If it accepts only
`Type`, handle absence with a safe call, Elvis operator, or explicit branch.

> **Pitfall:** An extension can use public receiver methods but can't access private or protected receiver state.
> Generated code often equates “member-call syntax” with “member privileges” and fails to compile.

**Fix:** Don't widen visibility merely to accommodate an extension.
Behavior that depends on private invariants belongs inside the type; caller-specific composition
should use only stable public APIs.

<!-- deep -->

## Two receivers in a member extension

An extension declared inside a class has two object contexts.
The instance of the type to the declaration's left is the extension receiver, while the instance
of the containing class is the dispatch receiver. The former supplies the object being extended;
the latter supplies the policy or environment that owns the group of extensions.

When both receivers have a member with the same name, the extension receiver's member wins.
Use a qualified `this`, such as `this@Formatter`, to select the dispatch receiver explicitly.
This style carries substantial implicit context, so it earns its place only when the extension
really belongs to a policy owned by that host object.

A member extension can vary through the dispatch receiver's virtual-member mechanism,
but the extension receiver is still selected statically. In other words, the host policy can
change dynamically while the runtime subtype of the extended object doesn't switch extension overloads.

This distinction is easy to blur in generated accessors, serialization policies, and DSL hosts.
During review, write down both receiver types instead of asking only “what is `this`?” If ownership
is still hard to read, an ordinary function with explicit parameters is often easier to maintain.

## Static resolution is also an evolution contract

Compatibility for a public extension depends on more than its own signature.
The receiver type's member set, caller imports, and same-named extensions from other libraries all
participate in source resolution. A seemingly unrelated dependency upgrade can make an old call
ambiguous or redirect it to a new member after recompilation.

Member precedence protects a class author's control over the class API, but it doesn't guarantee
unchanging caller behavior. Library authors should avoid semantically vague names on broad types,
especially `Any`, `String`, and `List`. Internal projects should use package boundaries and
visibility to state which domain owns an extension.

An import alias changes only the name used in the current file; it neither copies the function nor
changes its receiver rules. It can clearly distinguish two legitimate implementations, such as
encoders for different protocols. If the implementations represent mutually exclusive domain
semantics, a wrapper type or explicit service is usually a better long-term boundary.

Don't test this risk only by unit-testing the extension body.
Keep call-site tests that compile with real imports and real static types, and recompile them during
dependency upgrades. Those tests catch changes to the candidate set even when the extension body
itself is untouched.

## JVM representation is not language semantics

On Kotlin/JVM, a top-level extension function is typically lowered to a static method in a generated
file facade, with the extension receiver passed as a parameter. A member extension declared inside
a class also needs a host instance, so “all extensions become static methods” isn't an accurate rule.

The file name, `@file:JvmName`, visibility, and signature affect the Java-facing call form.
Java callers usually invoke a generated file-class method for a top-level extension rather than
using `receiver.extension()` syntax. Cross-language APIs should verify the actual generated JVM signature.

These are JVM backend representation details, not the reason Kotlin source selects an extension.
Kotlin/JS, Kotlin/Native, and other targets may use different representations, while language rules
such as member precedence and declared-receiver-type resolution still follow Kotlin semantics.

Explain scope and overload resolution first, then inspect bytecode when interop or debugging requires it.
Thinking of an extension as “a function with a receiver parameter” is useful; treating that model as
a fixed ABI across every target and declaration location leads to incorrect conclusions.

<!-- /deep -->

[Checkpoint: kotlin/extensions](https://codewiki.com/kotlin/extensions/#checkpoint)

## Further reading

- [Kotlin documentation: Extensions](https://kotlinlang.org/docs/extensions.html)
- [Kotlin coding conventions: Functions and extensions](https://kotlinlang.org/docs/coding-conventions.html)
- [Kotlin documentation: Packages and imports](https://kotlinlang.org/docs/packages.html#imports)
- [Kotlin language specification: Overload resolution](https://kotlinlang.org/spec/overload-resolution.html)
