What you'll learn
Extension functions are one of Kotlin's most loved features. They let you add new methods and properties to existing types — even ones from libraries you can't modify, like String or Int — without inheritance. Much of Kotlin's standard library is built this way.
By the end you'll be able to:
- Write extension functions on any type
- Add extension properties
- Understand how extensions are resolved (statically)
- Extend nullable types
Extension functions
You declare an extension by prefixing the function name with the receiver type and a dot: fun String.shout(). Inside, this refers to the receiver — the string you called it on. Then you call it with normal dot syntax, as if it had always been part of the type:
Existing type (you don't own it)
String
Your extension
fun String.shout()
New ability
"hi".shout()
// add a method to the existing String type — no subclassing needed
fun String.shout(): String = this.uppercase() + "!"
println("hello".shout()) // HELLO!
// extend Int too — 'this' is the number
fun Int.isEven(): Boolean = this % 2 == 0
println(4.isEven()) // true
println(7.isEven()) // falseExtension properties
You can add properties too — with a custom getter (they can't store state, only compute a value). This reads even more naturally than a function:
// an extension PROPERTY (must have a custom getter)
val String.firstHalf: String
get() = substring(0, length / 2)
println("Kotlin".firstHalf) // KotHow extensions resolve
A key detail: extensions are resolved statically, based on the declared type of the variable, not the runtime type. They also can't access an object's private members — they're syntactic sugar dispatched at compile time, not true methods added to the class.
Note
"hi".shout() into a plain static call like shout("hi") — it just reads like a method. That's why they're safe and can't break existing code.Nullable receivers
The receiver type can even be nullable (String?), which lets you handle the null case inside the extension — so callers don't need a safe call. Kotlin's own toString() on a nullable and String?.isNullOrEmpty() work exactly this way:
// the receiver type can even be nullable
fun String?.safeLength(): Int = this?.length ?: 0
val name: String? = null
println(name.safeLength()) // 0 — no crash, even on null!
println("Sara".safeLength()) // 4Tip
StringUtils.capitalize(name) (a Java-style static helper), you write name.capitalize() — which flows naturally and is discoverable via auto-complete. Group related extensions in a file named for what they extend.Recap & quick check
Key takeaways
- Extension functions add methods to existing types: fun String.shout() { ... }; 'this' is the receiver.
- Extension properties add computed properties via a custom getter (no stored state).
- Extensions resolve statically (by declared type) and can't access private members — they're compiler sugar.
- The receiver can be nullable (String?), letting the extension handle null itself.
- Extensions make code read naturally: name.capitalize() beats StringUtils.capitalize(name).
Quick check
1. What do extension functions let you do?
2. Inside 'fun String.shout()', what does 'this' refer to?
3. How are extension functions resolved?
4. Can an extension access an object's private members?
5. Why are extensions great for readability?
Excellent — extensions are pure Kotlin joy. Next up: Module 12 — Function Types, Inline & References, the finale of Phase 2.