0
0
Kotlinprogramming~15 mins

Extensions vs member functions priority in Kotlin - Trade-offs & Expert Analysis

Choose your learning style9 modes available
Overview - Extensions vs member functions priority
What is it?
In Kotlin, both member functions and extension functions can be called on an object. Member functions are defined inside a class, while extension functions are defined outside the class to add new functionality without modifying the original class. When both a member function and an extension function have the same name and signature, Kotlin decides which one to call based on a priority rule. This topic explains how Kotlin chooses between them.
Why it matters
Understanding which function Kotlin calls when both member and extension functions exist prevents bugs and confusion. Without this knowledge, developers might expect an extension function to run but the member function runs instead, leading to unexpected behavior. This clarity helps write safer, more predictable code and use extensions effectively.
Where it fits
Before learning this, you should know basic Kotlin syntax, classes, and functions, including how to define member and extension functions. After this, you can explore more advanced Kotlin features like higher-order functions, function overloading, and polymorphism.
Mental Model
Core Idea
Kotlin always prefers member functions over extension functions when both have the same name and signature on an object.
Think of it like...
It's like having a house with a built-in light switch (member function) and a remote control for the light (extension function). When you want to turn the light on, the built-in switch always takes priority because it's part of the house, while the remote is an add-on.
Object
 ├─ Member function (priority)
 └─ Extension function (fallback)

Call on object → Check member function first → If none, use extension function
Build-Up - 7 Steps
1
FoundationUnderstanding member functions in Kotlin
🤔
Concept: Member functions are functions defined inside a class and belong to its instances.
class Person(val name: String) { fun greet() = "Hello, $name!" } val p = Person("Anna") println(p.greet()) // Calls member function greet
Result
Hello, Anna!
Knowing member functions are part of the class helps understand their natural priority in Kotlin.
2
FoundationDefining and using extension functions
🤔
Concept: Extension functions add new functions to existing classes without changing their code.
fun Person.greet() = "Hi, $name!" val p = Person("Anna") println(p.greet()) // Calls extension function greet
Result
Hi, Anna!
Extension functions look like member functions but are defined outside the class, enabling flexible additions.
3
IntermediateConflict: member vs extension function call
🤔Before reading on: If both member and extension functions named greet() exist, which one do you think Kotlin calls? Member or extension?
Concept: When both member and extension functions with the same signature exist, Kotlin calls the member function.
class Person(val name: String) { fun greet() = "Hello, $name!" } fun Person.greet() = "Hi, $name!" val p = Person("Anna") println(p.greet()) // Which greet is called?
Result
Hello, Anna!
Understanding this priority avoids surprises when extensions seem ignored if a member function exists.
4
IntermediateExtension functions do not override members
🤔Do you think extension functions can replace or override member functions? Yes or No?
Concept: Extension functions cannot override or replace member functions; they are resolved statically and have lower priority.
class Person(val name: String) { fun greet() = "Hello, $name!" } fun Person.greet() = "Hi, $name!" val p: Person = Person("Anna") println(p.greet()) // Calls member greet // Even if you cast to a supertype, member still wins if present.
Result
Hello, Anna!
Knowing extensions are static and do not override members clarifies their role as helpers, not replacements.
5
IntermediateExtension functions on nullable receivers
🤔
Concept: Extension functions can be defined on nullable types, allowing safe calls on null objects.
fun Person?.greet() = if (this == null) "No person" else "Hi, ${this.name}!" val p: Person? = null println(p.greet()) // Calls extension on nullable receiver
Result
No person
Extensions can add functionality even when the object might be null, which member functions cannot do.
6
AdvancedStatic resolution of extension functions
🤔If you call an extension function on a variable typed as a superclass, but the actual object is a subclass, which extension is called? The one for the variable type or the object type?
Concept: Extension functions are resolved based on the compile-time type of the variable, not the runtime type of the object.
open class Shape class Circle : Shape() fun Shape.draw() = "Drawing shape" fun Circle.draw() = "Drawing circle" val shape: Shape = Circle() println(shape.draw()) // Calls Shape.draw(), not Circle.draw()
Result
Drawing shape
Understanding static resolution prevents confusion about polymorphism with extensions.
7
ExpertMember function priority in inheritance hierarchies
🤔If a subclass overrides a member function and an extension function with the same name exists for the superclass, which function is called on a subclass instance typed as superclass?
Concept: Member functions, including overrides, always have priority over extension functions, even in inheritance scenarios.
open class Base { open fun greet() = "Base greet" } class Derived : Base() { override fun greet() = "Derived greet" } fun Base.greet() = "Extension greet" val b: Base = Derived() println(b.greet()) // Calls Derived.greet() member function
Result
Derived greet
Knowing member functions override extensions even in polymorphism helps avoid subtle bugs.
Under the Hood
Kotlin resolves member functions dynamically at runtime using virtual dispatch, meaning the actual object's method runs. Extension functions are resolved statically at compile time based on the variable's declared type. Because member functions are part of the class's vtable, they have higher priority. Extensions are just static helper functions that look like members but do not participate in polymorphism or overriding.
Why designed this way?
This design keeps member functions true to object-oriented principles with dynamic dispatch, while extensions provide a safe way to add functionality without modifying classes. Allowing extensions to override members would break encapsulation and lead to confusing behavior. Static resolution of extensions also simplifies compiler implementation and improves performance.
Call on object
  │
  ├─ Check member function (dynamic dispatch)
  │     └─ If found, call member function
  └─ Else call extension function (static resolution)
Myth Busters - 4 Common Misconceptions
Do you think extension functions can override member functions? Commit to yes or no.
Common Belief:Extension functions can override or replace member functions if they have the same signature.
Tap to reveal reality
Reality:Extension functions cannot override member functions; member functions always have priority.
Why it matters:Believing extensions override members can cause developers to expect their extension code to run when it never does, leading to bugs.
If you call an extension function on a variable typed as a superclass but holding a subclass object, which extension runs? Commit to your answer.
Common Belief:The extension function for the actual object's class runs (dynamic dispatch).
Tap to reveal reality
Reality:Extension functions are resolved statically by the variable's declared type, not the runtime type.
Why it matters:This misconception leads to unexpected behavior when extensions seem not to adapt to subclass types.
Can extension functions be called on null objects? Yes or no?
Common Belief:Extension functions cannot be called on null objects because they are like member functions.
Tap to reveal reality
Reality:Extension functions can be defined on nullable receivers and safely called on null objects.
Why it matters:Not knowing this limits the use of extensions for null-safe operations.
If a subclass overrides a member function, does an extension function for the superclass get called on subclass instances? Commit your answer.
Common Belief:Extension functions for the superclass might override subclass member functions.
Tap to reveal reality
Reality:Subclass member functions always override extension functions for the superclass.
Why it matters:Misunderstanding this can cause confusion in inheritance and polymorphism scenarios.
Expert Zone
1
Extension functions are resolved based on the compile-time type, so casting variables can change which extension is called without changing the actual object.
2
Member functions participate in virtual dispatch, so overrides in subclasses are called dynamically, but extensions never do.
3
Extensions can add functionality to classes you don't own, but they cannot access private or protected members, preserving encapsulation.
When NOT to use
Do not rely on extension functions to change or override existing class behavior; use inheritance or delegation instead. Avoid extensions when you need polymorphic behavior or access to private class internals.
Production Patterns
In real-world Kotlin code, extensions are widely used to add utility functions to standard library classes or third-party libraries without inheritance. Member functions handle core behavior, while extensions provide helpers, DSLs, or fluent APIs. Understanding priority avoids bugs when extensions share names with members.
Connections
Method overriding in object-oriented programming
Member function priority is an example of method overriding, where subclass methods replace superclass methods dynamically.
Knowing how overriding works in OOP clarifies why member functions have priority over extensions, which are static.
Static vs dynamic dispatch
Extension functions use static dispatch, while member functions use dynamic dispatch.
Understanding dispatch types explains why extensions cannot override members and how Kotlin resolves calls.
Function overloading resolution in compilers
Both extension and member functions participate in overload resolution, but member functions have higher priority.
Compiler design principles behind overload resolution help understand Kotlin's call priority rules.
Common Pitfalls
#1Expecting an extension function to run when a member function with the same name exists.
Wrong approach:class Person { fun greet() = "Hello" } fun Person.greet() = "Hi" val p = Person() println(p.greet()) // Expects "Hi" but gets "Hello"
Correct approach:class Person { fun greet() = "Hello" } fun Person.sayHi() = "Hi" val p = Person() println(p.greet()) // Calls member greet println(p.sayHi()) // Calls extension sayHi
Root cause:Misunderstanding that member functions always have priority over extensions.
#2Assuming extension functions are dynamically dispatched like member functions.
Wrong approach:open class Shape class Circle : Shape() fun Shape.draw() = "Shape" fun Circle.draw() = "Circle" val s: Shape = Circle() println(s.draw()) // Expects "Circle" but gets "Shape"
Correct approach:open class Shape { open fun draw() = "Shape" } class Circle : Shape() { override fun draw() = "Circle" } val s: Shape = Circle() println(s.draw()) // Calls overridden member, prints "Circle"
Root cause:Not realizing extensions are resolved statically by variable type, not runtime type.
#3Trying to override private or protected member functions with extensions.
Wrong approach:class Secret { private fun reveal() = "Secret" } fun Secret.reveal() = "Not secret" // Cannot call reveal() from outside
Correct approach:class Secret { fun reveal() = "Secret" } fun Secret.reveal() = "Not secret" val s = Secret() println(s.reveal()) // Calls member reveal
Root cause:Extensions cannot access or override private/protected members, preserving encapsulation.
Key Takeaways
Kotlin always calls member functions before extension functions when both have the same name and signature.
Extension functions are resolved statically based on the variable's declared type, not the runtime type of the object.
Extensions cannot override or replace member functions; they are helpers that add functionality without changing class behavior.
Member functions participate in dynamic dispatch and can be overridden in subclasses, while extensions cannot.
Understanding these rules prevents bugs and helps use Kotlin extensions effectively and safely.