You maintain a public library. A consumer added an extension function `User.fullName()` years ago. Now you want to add a member `fun fullName()` to `User`. What happens to the consumer's code, and how do you reason about the compatibility impact?
answer
- New member shadows consumer's same-signature extension
- Source-compatible, but behavioral break possible
- Manifests on recompile; stale binaries keep extension
- Adding members is the SAFE direction by design
- Different name if semantics differ; document it
basics
~10 sOnce your member exists, calls of the same signature start hitting the member instead of the consumer's extension. The code still compiles, but its behavior may change because the member now wins.
solid answer
~50 sAdding a member with the **same signature** as a consumer's extension causes their call sites to **silently rebind** to your new member — the member-wins rule means `user.fullName()` now resolves to the member, and the extension is shadowed. This is **source-compatible** (it still compiles, no error) but potentially a **behavioral breaking change**: the result may differ from the extension's logic. It's also a **binary** consideration: recompiled callers pick up the member; already-compiled callers that invoked the extension via its static synthetic method keep calling the extension until recompiled. To manage this safely: communicate it as a behavioral change, ideally match the extension's documented semantics, consider a **different name** if semantics differ, use `@Deprecated`/release notes, and lean on Kotlin's guarantee that adding members is the *safe* direction precisely because members deterministically win. The flip side — relying on extensions to 'patch' a third-party class — is fragile for exactly this reason.
code
kotlin · 8 lines// Consumer code (pre-existing)
fun User.fullName() = "$firstName $lastName"
// Library adds, in a new version:
class User(val firstName: String, val lastName: String) {
fun fullName() = "$lastName, $firstName" // member now wins
}
// After recompile, user.fullName() returns "lastName, firstName" — silent behavior change.go deeper
Knows the member will start winning over the extension.
Notes the code still compiles but behavior can change because the member wins.
Separates source vs behavioral vs binary compatibility and gives recompile nuance.
Frames member-addition as the deliberately safe evolution direction, weighs naming/semantic strategy, and prescribes communication and semver handling for the silent behavioral change.
## The scenario A consumer wrote, in their codebase: ```kotlin fun User.fullName() = "$firstName $lastName" user.fullName() // calls the extension today ``` You now add to the library: ```kotlin class User(/* ... */) { fun fullName() = "$lastName, $firstName" // new member, same signature } ``` ## What happens at the call site By the **member-wins** rule, after the consumer **recompiles** against the new library version, `user.fullName()` resolves to the **member**. Their extension is now **shadowed** (the IDE flags it). The code **still compiles** — so it's **source-compatible** — but the **behavior changes** if the member returns something different (here, name order flips). That's a **silent behavioral break**. ## Source vs. binary compatibility - **Source compatibility:** preserved — no compile error; callers don't need code edits. - **Behavioral compatibility:** **not** guaranteed — the member may compute a different value. - **Binary nuance:** an extension compiles to a static synthetic method; a member is an instance method. Already-compiled call sites that bound to the extension's synthetic method keep calling it until recompiled. After recompilation they bind to the member. So the change manifests on **recompile**, not necessarily for stale binaries. ## Why this is the *safe* direction by design Kotlin deliberately makes **adding a member** the safe evolution step: because members deterministically win, a library can introduce behavior that callers transparently adopt, instead of extensions silently overriding the library. The dangerous direction is the opposite — depending on an **extension to override** a class you don't own, which the language forbids precisely to prevent hijacking. ## How to manage the change responsibly - **Match semantics** of the common community extension if one is widespread, to minimize behavioral surprise. - If semantics must differ, **choose a different name** so consumer extensions aren't captured. - **Document** in release notes; consider `@Deprecated` on a transitional API or a `ReplaceWith` hint where applicable. - Treat it as a **minor-but-behavioral** change in semver reasoning and call it out. - Encourage consumers not to rely on extensions to substitute for missing members of types they don't own. ## Mental model Think of the public type's member surface as the **authoritative contract**. Extensions are convenience sugar layered on top by callers; the language guarantees the contract owner wins, so evolving the contract is predictable — but predictability is not the same as behavioral transparency, which is the maintainer's job to communicate.
- Would this break already-compiled consumer binaries that aren't recompiled?Not immediately — a compiled call site bound to the extension's static synthetic method keeps calling it. The rebind to the member happens when the consumer recompiles against the new version.
- If you wanted zero behavioral risk, what's the safest move?Pick a different member name (or align the member exactly with the widespread extension's semantics) and document the addition; never assume callers' extensions match your intended behavior.
Your library is the law; consumer extensions are house rules. The moment the law speaks on a matter, the house rule is overridden — silently, but legally.
saying these in an interview costs you the question
- Claiming the consumer code stops compiling (it doesn't)
- Saying the extension still wins after recompilation
- Ignoring the behavioral-vs-source compatibility distinction
- Treating it as purely binary with no recompile nuance
- Recommending extensions to 'override' library behavior