Extension Functions: Add Methods to Classes You Don't Own


You can’t add a method to String. It’s in the standard library; you don’t own it. So in Java you write StringUtils.capitalize(name) — a free function wearing a class as a costume, called in the wrong order, with the thing you care about buried as the first argument. Kotlin lets you write name.capitalize() instead, even though String isn’t yours. The mechanism is the extension function, and once you see it you’ll spot it everywhere — most of Kotlin’s own standard library is built from them.

This post follows interfaces. Both add behavior to types; extensions do it without touching the type at all.

Declaring one

You write a normal function, but prefix the name with the type you’re extending — the receiver. Inside the body, this refers to the instance it was called on:

fun String.shout(): String = this.uppercase() + "!"

"hello".shout()        // "HELLO!"

As with any function, this is often implicit, so the body usually reads as if it were a real method:

fun String.shout(): String = uppercase() + "!"

That’s the whole idea. shout isn’t added to the String class — but at the call site it’s indistinguishable from a method, and that’s what makes code read in the natural subject-verb order.

It’s just a function in disguise

An extension doesn’t modify the class and can’t see its private members. Under the hood it compiles to a plain static function that takes the receiver as its first argument — exactly the StringUtils.capitalize(name) shape, but with the ergonomics flipped. Two consequences follow from that.

First, extensions resolve statically, by the declared type of the variable — not virtually, by the runtime type. This surprises people:

open class Animal
class Dog : Animal()

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

val pet: Animal = Dog()
pet.speak()            // "generic noise" — chosen by the Animal type

A real overridden method would print woof. An extension is picked by the type the compiler sees, so if you want polymorphism, use a member function or interface instead.

Second, a member always wins. If the class already has a method with the same signature, the member is called and your extension is quietly ignored. You can’t accidentally override real behavior.

Extension properties

The same trick works for properties, as long as they compute their value — there’s nowhere to store a new field:

val String.firstWord: String
    get() = substringBefore(" ")

"hello world".firstWord    // "hello"

Nullable receivers

You can extend a nullable type, which lets the extension itself handle null — the call is safe even on a null reference:

fun String?.orEmpty(): String = this ?: ""

val name: String? = null
name.orEmpty()         // "" — no crash, no safe call needed

Inside such an extension, this can be null, so you check it like any other nullable value. This is exactly how the standard library’s own isNullOrEmpty() works.

Where they earn their keep

Extensions are how you keep call sites reading top-to-bottom, left-to-right, instead of inside-out. They’re ideal for small, focused helpers on existing types, and for scoping a helper to the file or class where it’s relevant rather than dumping it in a global Utils bucket. Kotlin’s standard library leans on them so heavily that listOf(...).filter { }.map { }.first() is almost entirely extension calls.

The one discipline: an extension is in scope only where it’s imported, so don’t reach for one to express core behavior of a type you do own — that belongs in the class. Use extensions to augment, not to architect.

Final thoughts

The extension function quietly retires one of Java’s most tired patterns — the static utility class — and replaces it with calls that read like the methods you wished the type had. The catch worth remembering is that they’re static, not virtual: great for adding convenience, wrong for adding polymorphism. Keep that line straight and they’re one of the features you’ll miss most going back to Java.

Next, a gotcha that bites every Java developer eventually: what == actually means in Kotlin. That’s equality.

Practice: reinforce this with the companion workbook — short, click-to-reveal problems.