skip to content

Contrast how Kotlin resolves an overridden open member function versus an extension function declared on the same hierarchy. Show what changes when only one of them is an extension.

level: middleimportance: must knowfreq 60%

answer

  1. Member override = runtime class
  2. Extension = declared type
  3. Member wins over same-signature extension
  4. Extensions can't override, only shadow
  5. Don't swap member<->extension blindly

basics

~10 s

Overridden members run based on the real object (virtual dispatch). Extensions run based on the variable's declared type (static). So members can be polymorphic; extensions cannot.

solid answer

~40 s

An `open` member function that a subclass `override`s is resolved by **virtual dispatch**: at runtime the JVM uses the object's actual class to pick the most-derived override, so `Animal` typed variable holding a `Dog` calls `Dog`'s override. An **extension** is resolved **statically** from the declared receiver type, so the same `Animal`-typed variable always calls the `Animal` extension regardless of the real object. The practical consequence: if you build an API on overridable members you get polymorphism; if you build it on extensions you lock behavior to the declared type. Mixing them is confusing — converting a member to an extension (or vice versa) silently changes dispatch semantics. Extensions also can't be `override`n; a more specific extension only **shadows** a less specific one within a given declared type, never overrides it.

code

kotlin · 11 lines
kotlin
open class Animal { open fun memberSpeak() = "animal-member" }
class Dog : Animal() { override fun memberSpeak() = "dog-member" }

fun Animal.extSpeak() = "animal-ext"
fun Dog.extSpeak() = "dog-ext"

fun main() {
    val pet: Animal = Dog()
    println(pet.memberSpeak()) // dog-member (virtual)
    println(pet.extSpeak())    // animal-ext (static)
}

go deeper

for a junior

Can state members are virtual and extensions are static, even if shaky on precedence details.

for a middle

Correctly predicts both outputs in the experiment and knows member-vs-extension precedence.

for a senior

Explains the bytecode mechanism, the shadowing-vs-overriding distinction, and the refactoring hazard of swapping them.

for a principal

Sets team conventions about when to expose behavior as members vs extensions to keep dispatch semantics predictable across an API surface.

## Two resolution mechanisms **Virtual dispatch (members):** A function marked `open` and `override`n is stored in the class's virtual method table. At runtime the JVM looks at the object's **actual class** and calls the most-specific override. This is polymorphism. **Static resolution (extensions):** An extension compiles to a static function taking the receiver as a hidden first parameter. The compiler binds the call using the **declared (compile-time) type** of the receiver expression. No runtime lookup occurs. ## Side-by-side experiment ```kotlin open class Animal { open fun memberSpeak() = "animal-member" } class Dog : Animal() { override fun memberSpeak() = "dog-member" } fun Animal.extSpeak() = "animal-ext" fun Dog.extSpeak() = "dog-ext" fun main() { val pet: Animal = Dog() println(pet.memberSpeak()) // "dog-member" -> virtual, uses runtime class println(pet.extSpeak()) // "animal-ext" -> static, uses declared type } ``` Same variable, same object — opposite outcomes. The member call sees the `Dog`; the extension sees the `Animal`. ## What happens when a member and an extension collide If a class declares a **member** function with the same signature as an in-scope **extension**, the **member always wins** (member-vs-extension precedence). The extension becomes effectively unreachable through normal dot calls on that type. This is another reason mixing the two is risky: adding a member later can silently shadow an extension your code relied on. ## Extensions can't override ```kotlin // fun Dog.extSpeak() does NOT override fun Animal.extSpeak(); // it only shadows it when the declared type is Dog. ``` There is no `override` keyword for extensions, no late binding, no super-call chain. ## Design takeaway - Need polymorphic, subtype-aware behavior -> use **open members**. - Need a convenience helper that doesn't pollute the type and behaves uniformly per declared type -> use an **extension**, and document that it is not virtual. - Don't silently convert one into the other; dispatch semantics change.

  • A class has both a member `fun describe()` and an in-scope extension `fun MyType.describe()`. Which is called and why?
    The member. Members take precedence over extensions with the same signature; the extension is shadowed for dot calls on that type.
  • Can `fun Dog.extSpeak()` use `super` to call `fun Animal.extSpeak()`?
    No. Extensions have no override/super mechanism. You'd have to call the function with an `Animal`-typed receiver explicitly.

saying these in an interview costs you the question

  • Saying extensions can override members
  • Claiming the extension wins over a same-signature member
  • Treating extension dispatch as polymorphic
  • Not knowing members use the runtime class while extensions use declared type
  • Asserting extensions appear in the vtable

context