Extension Functions

Reviewed & published by Brayan K

Kotlin is a modern, concise language that lets you add new functions and properties to existing types — even ones you didn't write — without inheritance, through extension functions.

Part of the free Kotlin course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll write extension functions and properties, use the this receiver, and understand how they're resolved.

What You'll Learn in This Lesson

1️⃣ Extension Functions

An extension function is declared as fun ReceiverType.name(...). Inside, this refers to the value it's called on (the receiver). You can extend any type — your own classes or library types like String and Int.

// An extension function adds a method to an existing type WITHOUT
// editing or subclassing it. 'this' refers to the receiver.
fun String.shout(): String = this.uppercase() + "!"

// You can extend any type, including library types like Int.
fun Int.isEven(): Boolean = this % 2 == 0

// Extensions can take parameters too.
fun String.repeatWith(separator: String, times: Int): String =
    List(times) { this }.joinToString(separator)

fun main() {
    println("hello".shout())            // -> HELLO!
    println(4.isEven())                 // -> true
    println(7.isEven())                 // -> false
    println("ha".repeatWith("-", 3))    // -> ha-ha-ha
}

These read as if the methods always existed on the type — that's the whole appeal. Your IDE even lists them in autocomplete.

2️⃣ Extension Properties and Nullable Receivers

You can also add extension properties (computed, since they can't store state) and extend collections or even nullable types — letting an extension handle null itself.

// Extension PROPERTIES add computed values (no backing field allowed).
val String.firstWord: String
    get() = this.split(" ").first()

// Extensions on collections compose with the standard library.
fun List<Int>.average2dp(): String {
    val avg = if (isEmpty()) 0.0 else sum().toDouble() / size
    return "%.2f".format(avg)
}

// Extensions on nullable receivers can handle null themselves:
fun String?.orPlaceholder(): String = this ?: "(none)"

fun main() {
    println("Kotlin is great".firstWord)     // -> Kotlin
    println(listOf(1, 2, 3, 4).average2dp())  // -> 2.50

    val name: String? = null
    println(name.orPlaceholder())             // -> (none)
}

The String?.orPlaceholder() extension is callable on a null value because its receiver type is nullable — handy for clean null handling.

Your turn. Replace the TODO, then run and compare.

// 🎯 YOUR TURN — replace each TODO then run main()

// 1) Add an extension that doubles an Int.
fun Int.doubled(): Int = TODO * 2     // 👉 replace TODO with 'this'

// 2) Add an extension property: the last character of a String.
val String.lastChar: Char
    get() = this[this.length - 1]

fun main() {
    println(21.doubled())              // expect 42
    println("Kotlin".lastChar)         // expect n
}

Mini-Challenge: String Helpers

Write one extension function and one extension property on String.

// 🎯 MINI-CHALLENGE: string helpers.
// Save as Strings.kt and run main().
//
// 1. Add fun String.wordCount(): Int that returns the number of
//    space-separated words (this.trim().split(" ").size — assume
//    non-empty input).
// 2. Add an extension property val String.isPalindrome: Boolean
//    that is true when the string equals its reverse (use .reversed()).
// 3. In main(), print "racecar".isPalindrome and
//    "the quick fox".wordCount().
//
// ✅ Example output:
//    true
//    3

fun main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Extensions

ConceptKotlin Syntax
Extension functionfun String.shout() = ...
Receiverthis
With parametersfun Int.times(n: Int)
Extension propertyval String.firstWord get() = ...
Nullable receiverfun String?.orElse()

🎉 Lesson Complete!

Practice quiz

What does an extension function let you do?

  • Add a function to an existing type without editing or subclassing it
  • Rewrite the original class source
  • Make a class final
  • Remove methods from a type

Answer: Add a function to an existing type without editing or subclassing it. Extensions add methods to existing types without modifying or subclassing them.

Inside an extension function, what does 'this' refer to?

  • The enclosing file
  • The receiver — the value it was called on
  • Always null
  • The companion object

Answer: The receiver — the value it was called on. this refers to the receiver, the value the extension is called on.

How are extension functions dispatched?

  • Dynamically at runtime
  • By reflection only
  • Statically, based on the declared type of the receiver
  • Randomly

Answer: Statically, based on the declared type of the receiver. Extensions resolve statically using the declared (compile-time) type of the receiver.

Why can't an extension property have a backing field?

  • Because they are always val
  • Because the extension is not part of the original class, so there is nowhere to store state
  • Because Kotlin forbids properties entirely
  • Because they must be private

Answer: Because the extension is not part of the original class, so there is nowhere to store state. An extension isn't part of the class, so it has no place to store state; it must be computed.

If a member function and an extension have the same name, which is called?

  • The extension always
  • The member function wins
  • Whichever is imported last
  • It is a compile error

Answer: The member function wins. Members win over extensions when both have the same signature.

How can an extension be called safely on a possibly-null value?

  • It cannot ever be called on null
  • By declaring the receiver type as nullable (Type?)
  • By marking it private
  • By making it a member

Answer: By declaring the receiver type as nullable (Type?). An extension on a nullable receiver (Type?) can handle null itself.

How does the compiler implement myString.shout()?

  • As a static call passing the string as a hidden receiver argument
  • By editing the String class
  • By creating a subclass of String
  • By boxing into an Object

Answer: As a static call passing the string as a hidden receiver argument. The call compiles to a static call with the receiver passed as a hidden first argument.

Can extension functions take parameters?

  • No, never
  • Only one parameter
  • Only parameters of the receiver type
  • Yes, like any function

Answer: Yes, like any function. Extensions can take parameters just like ordinary functions.

What must happen for an extension to be usable elsewhere?

  • It must be imported (or in the same file/package)
  • It must be public and abstract
  • It must be inside a data class
  • Nothing; all extensions are global

Answer: It must be imported (or in the same file/package). Extensions must be imported or be in the same file/package to be visible.

When should you prefer a member function over an extension?

  • Never; extensions are always better
  • When you need true runtime polymorphism via overriding
  • When the receiver is a String
  • When you want IDE autocomplete

Answer: When you need true runtime polymorphism via overriding. For polymorphic behaviour chosen at runtime, use an open member function and override it.

Continue this course

Frequently asked questions

How can you add a function to a class you didn't write?

Extension functions don't actually modify the original class. Instead, the compiler resolves myString.shout() into a static call that passes the string as a hidden first argument (the receiver, accessible as this). So you get method-call syntax and discoverability — your IDE lists the extension in autocomplete — without touching the original type's source. This works on standard-library and third-party types just as well as your own.

Are extension functions resolved statically or dynamically?

Statically. Which extension function runs is decided at compile time based on the declared type of the receiver, not the runtime type. This differs from member functions, which dispatch dynamically (polymorphically). In practice this means extensions are great for adding utility behaviour, but if you need true polymorphism — different behaviour per subclass chosen at runtime — you should use an open member function and override it instead.

What is an extension property and what can't it do?

An extension property adds a property-style accessor to an existing type, defined with a custom getter (and optionally a setter). Because the extension isn't part of the original class, it cannot have a backing field — there's nowhere to store state. So extension properties must be computed: val String.firstWord get() = split(" ").first() derives its value each time. If you need to store data alongside an object you didn't write, you'll need a different approach, such as a wrapper class or a map.

When should I prefer an extension over a regular function?

Reach for an extension when the operation logically reads as 'something you do to a value of that type' and benefits from method-call syntax and IDE discoverability — for example "hello".shout() reads better than shout("hello"). Extensions also let you build fluent, chainable APIs. Use a plain top-level function when there's no natural single receiver, or when the logic involves several unrelated inputs equally. Extensions are heavily used across the Kotlin standard library for exactly this reason.