skip to content

Show how a member function and an extension function with the SAME name but DIFFERENT signatures can coexist. Walk through how overload resolution decides which to call.

level: middleimportance: should knowfreq 50%

answer

  1. Different params = overload set
  2. Resolution: applicable, then most specific
  3. Members are a higher-priority group than extensions
  4. If member is applicable, extension loses even if more specific
  5. Make member non-applicable to force the extension

basics

~10 s

If the parameters differ, the member and the extension are just two overloads. Kotlin picks by matching your arguments to the parameter lists, so both can be reachable depending on what you pass.

solid answer

~40 s

The member-wins rule only triggers on an **exact-signature collision**. When the extension's parameter list differs, the member and extension form an ordinary **overload set**, and standard **overload resolution** applies: the compiler chooses the most specific applicable candidate for the supplied arguments. So `obj.foo()` may hit the member while `obj.foo(x)` hits the extension. Resolution considers arity, parameter types, default values, and varargs, and prefers the most specific match. One subtlety: when a call is applicable to **both** a member and an extension of the same name, **members are preferred** during candidate ranking even before specificity tie-breaks — extensions are considered a lower-priority group. So if an argument set is applicable to both a member overload and an extension overload, the member is chosen. Genuinely distinct signatures avoid this and let the extension be selected normally.

code

kotlin · 9 lines
kotlin
class C {
    fun f(x: Any) = "member Any"
}
fun C.f(x: String) = "ext String"

fun main() {
    // member f(Any) is applicable to a String; members win the group
    println(C().f("hi")) // "member Any", not "ext String"
}

go deeper

for a junior

Knows that different parameters let both exist as overloads.

for a middle

Explains applicable-then-most-specific resolution and demonstrates a working overload pair.

for a senior

Knows the member-group preference can beat a more-specific extension and how to design around it.

for a principal

Reasons about API design pitfalls: how adding members later can quietly capture calls previously served by extensions, and guards against it in public APIs.

## Same name, different signature = overloads If the **parameter lists differ**, there is no collision. The member and the extension become an **overload set** for that name, and Kotlin runs **overload resolution** at each call site. ```kotlin class Logger { fun log() = "member: no-arg" } fun Logger.log(msg: String) = "extension: $msg" fun main() { val l = Logger() println(l.log()) // member (no args) println(l.log("boom")) // extension (String arg) } ``` ## How overload resolution picks The compiler, at the call site, builds the set of **applicable candidates** (those whose parameters can accept the given arguments, considering implicit conversions like `Int`→`Long` only where allowed, defaults, and varargs) and then chooses the **most specific** one. Key inputs: - **arity** (number of arguments), - **parameter types** (exact and subtype matches), - **default parameter values** (a candidate can be applicable with fewer supplied args), - **varargs** (`vararg`). ## The member-preference tie-break There is an important ranking rule: **members outrank extensions**. If, for a given argument list, both a **member** overload and an **extension** overload are applicable, the **member is chosen** — extensions sit in a lower-priority group and are only consulted when no member is applicable. So overlapping-but-not-identical signatures can still surprise you: ```kotlin class C { fun f(x: Any) = "member Any" } fun C.f(x: String) = "ext String" fun main() { println(C().f("hi")) // "member Any" // Member f(Any) is applicable to a String, and members win the group, // so the more 'specific' extension f(String) is NOT chosen. } ``` This is the practical reason "member wins" is repeated as a rule of thumb: even when signatures differ but the member is *applicable*, the member group is preferred over the extension group. ## Takeaways - Different, **non-overlapping** signatures → extension is freely callable. - **Overlapping** signatures where a member is applicable → member still wins the group, regardless of which looks more specific. - To force the extension, make its signature so the member is **not applicable** (e.g. an arg the member can't accept), or call it as a plain function `pkg.f(receiver, arg)`.

  • If the member were `fun f(x: Int)` instead of `fun f(x: Any)`, what would `C().f("hi")` call?
    The extension `f(String)`. The member `f(Int)` is not applicable to a `String` argument, so it leaves the overload group, and the extension is the only applicable candidate.
  • Does adding a default parameter change applicability?
    Yes. A member with a default value can be applicable with fewer arguments, potentially making it the preferred member candidate for a call that also matches an extension.

saying these in an interview costs you the question

  • Assuming the most specific candidate always wins, ignoring the member-group preference
  • Thinking different names are required to coexist (only signatures must differ)
  • Believing the extension is uncallable whenever a member shares the name
  • Forgetting defaults/varargs affect applicability
  • Claiming order of declaration influences the choice

context