Phase 2 · Functions & FunctionalModule 11~32 min read

Extension Functions & Properties

Add methods and properties to existing types — even ones you don't own.

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:

Extending a type you don't own

Existing type (you don't own it)

String

+

Your extension

fun String.shout()

=

New ability

"hi".shout()

Extensions.kt
// 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())        // false

Extension 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:

ExtProp.kt
// an extension PROPERTY (must have a custom getter)
val String.firstHalf: String
    get() = substring(0, length / 2)

println("Kotlin".firstHalf)   // Kot

How 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

Extensions don't actually modify the original type or its bytecode. The compiler turns "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:

NullableExt.kt
// 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())  // 4

Tip

Extensions shine for readability. Instead of 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.