Extension Functions and Scope Functions
|
This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Extension functions let you add a method to a type you do not own; scope functions — built entirely as extension functions themselves — give a short, consistent vocabulary for running a block of code against one value.
Extension Functions and Properties
An extension function is declared outside the class it appears to extend, with the receiver type prefixed
before the function name (ReceiverType.functionName). It compiles to an ordinary static-style function taking
the receiver as a hidden first parameter — no subclassing, and no modification of the original type, actually
happens:
fun String.isPalindrome(): Boolean {
val cleaned = this.lowercase().filter { it.isLetter() }
return cleaned == cleaned.reversed()
}
println("Racecar".isPalindrome()) // true -- called exactly like a real member function
val String.wordCount: Int // extension property -- no backing field allowed (see Classes and Objects)
get() = trim().split(Regex("\\s+")).size
println("the quick brown fox".wordCount) // 4
This is how much of the Kotlin standard library extends java.lang.String/java.util.Collection and friends
with idiomatic-feeling members (.trim(), .filter { }, .sum()) without touching the JDK classes themselves — see Strings and Text and
Collections and Sequences for many such examples in
practice. An extension is resolved statically, by the declared type at the call site, not dynamically by the
runtime type — unlike a real member override.
The Five Scope Functions
let, run, with, apply, and also all execute a lambda "in the context of" a value; they differ only in
(a) how the value is referenced inside the lambda (it vs. this) and (b) what the whole expression returns
(the lambda’s result vs. the original value):
| Function | Receiver as | Returns | Typical use |
|---|---|---|---|
|
|
lambda result |
null-checking a nullable value, or transforming it into something else — |
|
|
lambda result |
initializing then computing a result from an object in one expression. |
|
|
lambda result |
calling several members on an existing (non-null) object without repeating its name — not an extension
function itself, called as |
|
|
the object itself |
configuring an object’s properties right after construction, then returning it (chainable). |
|
|
the object itself |
a side effect (logging, an assertion) in the middle of a chain, without breaking the chain. |
// let: transform a nullable value only if it isn't null
val length: Int? = maybeName?.let { it.trim().length }
// run: compute a result using "this"
val area = Rectangle(3, 4).run { width * height }
// with: several calls on one object, no "obj." prefix needed
val summary = with(StringBuilder()) {
append("Name: ").append(name)
append(", Age: ").append(age)
toString()
}
// apply: configure, then return the same object -- classic builder-style usage
val request = HttpRequestBuilder().apply {
url = "https://example.com"
method = "GET"
}
// also: a side effect that doesn't change what's returned
val user = createUser("Ada").also { println("created user ${it.id}") }
Choosing Between Them
A practical rule of thumb: use apply/also when the point is the object itself (configuring it, or
observing it, before continuing to use it); use let/run when the point is the result of a computation
performed against it; reach for with specifically when calling several members on an object you already have
in hand (not one you just created). All five are ordinary inline extension functions in the standard library — nothing about them is special-cased by the compiler beyond inline itself (see
Lambdas and Higher-Order Functions).
See Also
-
Lambdas and Higher-Order Functions — how
inlineavoids per-call lambda-object overhead for functions like these. -
Null Safety —
?.let { }as the standard null-check idiom. -
Type-Safe Builders and DSLs — lambdas with receiver, the mechanism
run/with/applyare all built on.