Kotlin Extension Functions Cheat Sheet
Covers defining extension functions and properties, receiver types, extension resolution rules, and common standard library extension idioms in Kotlin.
Basic Extension Function
Adding a function to an existing type without subclassing it.
// Adds a function to String without modifying its sourcefun String.isPalindrome(): Boolean { val cleaned = this.lowercase().filter { it.isLetterOrDigit() } return cleaned == cleaned.reversed()}println("Racecar".isPalindrome()) // true// Extension with a parameterfun Int.times(action: () -> Unit) { repeat(this) { action() }}3.times { println("Hi") }
Extension Properties
Computed properties added to an existing type.
// Extension properties cannot have backing fields; must be computedval String.lastChar: Char get() = this[length - 1]println("Kotlin".lastChar) // 'n'val <T> List<T>.secondOrNull: T? get() = if (size >= 2) this[1] else nullprintln(listOf(1, 2, 3).secondOrNull) // 2
Resolution Rules
How Kotlin decides which extension function to call.
- Statically resolved- Extension functions are resolved at compile time based on the declared (static) type
- Member functions win- If a class has a member with the same signature, the member always takes priority
- No true polymorphism- Calling an extension on a variable typed as the base class uses the base class's extension, not the runtime type's
- Nullable receiver allowed- `fun Any?.safePrint()` can be called on a null reference and check `this == null` inside
- Import required- Extensions from another package must be imported (or in scope) to be visible
Scope Functions
let, run, with, apply, and also — extension functions in the stdlib.
val result = "hello".let { println("Length: ${it.length}") it.uppercase() // returned as result}data class Person(var name: String, var age: Int)val person = Person("Ana", 30).apply { age = 31 // `this` refers to the receiver; returns the receiver itself}val message = with(person) { "$name is $age years old" // `this` is implicit inside with}person.also { println("Created: $it") // returns the receiver, for side effects/chaining}
Common stdlib Extensions
Widely used extension functions from the Kotlin standard library.
- String.trim() / isBlank() / isNotEmpty()- Common string extension helpers in kotlin.text
- Iterable<T>.map/filter/fold- Higher-order extension functions on collections
- Any?.let { }- Runs a block with the receiver as `it`, useful for null-checked chains
- Int.coerceIn(min, max)- Clamps a numeric value within a range
- File.readText()- kotlin.io extension for reading a file's contents as a String
- Collection<T>.firstOrNull { }- Returns the first matching element or null instead of throwing
Reified Generics in Inline Extensions
Using inline + reified to access the generic type at runtime from an extension function.
// Normal generics erase T at runtime; inline + reified keeps it availableinline fun <reified T> Any?.isInstanceOf(): Boolean = this is Tprintln("hi".isInstanceOf<String>()) // trueprintln(42.isInstanceOf<String>()) // false// Common real-world use: a typed JSON decode extensioninline fun <reified T> String.decodeJson(): T = Json.decodeFromString(serializer<T>(), this)val user: User = """{"id":1,"name":"Ana"}""".decodeJson()// reified only works on `inline` functions -- the compiler substitutes// the concrete type at every call site, so there is no runtime overhead
Extension Lambdas for Type-Safe Builders
Function types with a receiver let extensions power Kotlin's DSL builder pattern.
class HtmlBuilder { private val children = mutableListOf<String>() fun p(text: String) { children += "<p>$text</p>" } fun build() = children.joinToString("")}// The lambda parameter type is `HtmlBuilder.() -> Unit`: an extension// function type, so `this` inside the block is the HtmlBuilder receiverfun html(block: HtmlBuilder.() -> Unit): String { val builder = HtmlBuilder() builder.block() // calling the lambda AS an extension on builder return builder.build()}val page = html { p("Hello") // implicit receiver, no `builder.` prefix needed p("World")}
Advanced Gotchas
Subtleties that trip up experienced Kotlin developers.
- Extensions cannot be overridden virtually- Declaring the same extension in a subclass shadows, it does not override; dispatch is still static
- Companion object extensions- `fun MyClass.Companion.create(): MyClass` requires MyClass to declare a `companion object`, even an empty one, to attach to
- Extension + member with defaults- A member function with default parameters still wins over an extension even when called with fewer arguments than the member needs
- Local extension functions- Extensions can be declared inside another function body, scoping their visibility to that function only
- Nullable receiver duplication- `fun String.foo()` and `fun String?.foo()` are distinct overloads; the compiler picks based on inferred nullability of the receiver expression
- Extensions on type aliases- Extending a typealias extends the underlying type globally, it does not create a distinct extension scoped to the alias
Operator Extension Functions
Overloading operators on types you don't own by declaring them as extensions.
data class Point(val x: Int, val y: Int)// operator extensions let you add operator overloads to existing classesoperator fun Point.plus(other: Point) = Point(x + other.x, y + other.y)operator fun Point.unaryMinus() = Point(-x, -y)operator fun Point.component1() = xoperator fun Point.component2() = yval p = Point(1, 2) + Point(3, 4) // Point(4, 6)val (px, py) = p // destructuring via component1/2// invoke as an extension makes any object callable like a functionoperator fun ((Int) -> Int).invokeTwice(x: Int) = this(this(x))val double: (Int) -> Int = { it * 2 }println(double.invokeTwice(3)) // 12
Extensions with Bounded Type Parameters
Constraining an extension's applicability to a subset of a generic hierarchy.
// Only applies to lists whose element type is Comparablefun <T : Comparable<T>> List<T>.isSorted(): Boolean = zipWithNext().all { (a, b) -> a <= b }println(listOf(1, 2, 3).isSorted()) // trueprintln(listOf("c", "a").isSorted()) // false// Multiple bounds require a `where` clausefun <T> T.describe(): String where T : Comparable<T>, T : CharSequence = "Comparable CharSequence of length $length"println("abc".describe())
Because extension resolution is static, never rely on an extension function to behave polymorphically across a class hierarchy — if you need runtime-type-dependent behavior, use a real member function or override, not an extension.