Skip to content
Extension Functions and Properties
step 1/7

Reading — step 1 of 7

Learn

~3 min readGenerics, Delegation, Extensions

Kotlin's extension functions let you add methods to existing types without modifying them. The standard library uses them everywhere — String.toIntOrNull(), List.sumOf {}, Iterable.groupBy {} — all extensions.

Defining an extension function

fun String.exclaim(): String = "$this!"

fun main() {
    println("hello".exclaim())     // "hello!"
}

The String. prefix says "add this function to String." Inside, this refers to the receiver — the String it was called on.

No modification to String. No subclassing. Pure compile-time sugar — at the bytecode level, "hello".exclaim() becomes exclaim("hello").

Generic extensions

fun <T> List<T>.second(): T? {
    return if (size >= 2) this[1] else null
}

listOf(1, 2, 3).second()        // 2
listOf<String>().second()        // null

Works for any List<T>.

Extension properties

val String.lastChar: Char
    get() = this[this.length - 1]

"hello".lastChar       // 'o'

Extension properties can't have backing fields — they must be computed via getter (and optional setter for var).

Bound vs unbound, dispatch

Extensions are statically dispatched — the type at the CALL site decides which extension runs:

open class Animal
class Dog : Animal()

fun Animal.sound() = "generic noise"
fun Dog.sound() = "woof"

val a: Animal = Dog()
println(a.sound())     // "generic noise" — STATIC type is Animal

Different from member functions which are dynamically dispatched. Often surprises Java refugees.

When to use extensions vs methods

Extensions:

  • Adding utility methods to library types you don't own (String, List, JDK classes)
  • Pure functions that read state and produce a result
  • Domain-specific helpers grouped by receiver type

Methods:

  • Behavior central to the class (validation, mutation, lifecycle)
  • Anything that needs polymorphism or override
  • API contract methods

Scope

Extensions are functions in a package — declared at top level OR inside a class (member extension). Top-level extensions are the most common:

// In file utils.kt:
package com.example.utils

fun String.titleCase(): String = ...

Import with import com.example.utils.titleCase at the call site.

Member extensions — receivers within receivers

class HttpClient {
    fun String.encoded(): String = URLEncoder.encode(this, "UTF-8")
    
    fun get(path: String, params: Map<String, String>) {
        val query = params.entries.joinToString("&") { (k, v) -> "${k.encoded()}=${v.encoded()}" }
        // ...
    }
}

The extension String.encoded() is only available inside HttpClient. Useful for DSLs and grouping context-specific helpers.

Standard library examples

// All extensions:
"hello".uppercase()
listOf(1, 2, 3).sumOf { it * 2 }
mapOf(1 to "one").mapValues { it.value.uppercase() }
Iterable<T>::filter, ::map, ::groupBy, ::partition, ::count
String::isNullOrBlank, ::toIntOrNull, ::reversed

Kotlin's stdlib gets its concise feel from extension functions. Java's String has ~70 methods; Kotlin adds another ~150 useful extensions on top.

Common mistakes

  • Expecting polymorphic dispatch — extensions are static. Override member methods, not extensions.
  • Defining extensions in the wrong file — must import at call site. IDE auto-imports help.
  • Naming collisions with member methods — member methods always WIN. The extension is shadowed silently.
  • Adding null to receiver type unintentionallyfun String?.isEmpty(): Boolean accepts null receivers. Different from String.isEmpty().
  • Overusing extensions for major logic — extensions are sugar; major behavior should live in classes.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…