skip to content

A type acquires two interfaces that each supply a body for a method with the same name and signature. Compare where different languages report that conflict and what they force the programmer to write.

level: seniorimportance: should knowfreq 40%

answer

  1. Java: error at the class, fix with Iface.super.m()
  2. class wins over interface default; more specific interface wins
  3. C#: most specific, and only callable via an interface-typed reference
  4. Rust: impls fine, ambiguity at the call, <S as A>::m(&s)
  5. Swift: extension-only members bind statically, may not error

basics

~20 s

Java reports it at the class declaration and makes you override, disambiguating with Iface.super.m(). C# prefers the most specific implementation or errors, and the body is reachable only through an interface-typed reference. Rust defers to the call site and wants fully-qualified syntax. Swift extension members may resolve silently.

solid answer

~50 s

The interesting variable is **where** the ambiguity is reported. - **Java 8+**: at the **class declaration**. A class inheriting two unrelated same-signature defaults does not compile until it overrides, typically delegating with `A.super.m()`. Consequence: an upstream library adding a default can break a downstream class that compiled fine yesterday. - **C# 8+**: the **most specific implementation** wins when one interface derives from the other; unrelated conflicts are an error. Default bodies are not class members, so you must hold the object through an interface-typed reference to reach one — two call sites with different static types can run different code. - **Rust**: the `impl` blocks compile happily; ambiguity is reported **only where you call**, and you resolve it with `<S as A>::m(&s)`. The type is fine, the call site is not. - **Swift**: protocol-extension members that are not requirements bind on the static type, so the "winner" may be neither conformer's method. (Scala instead linearises the conflict away with `super[T].m()` — a mixin rule, covered separately.)

code

rust · 9 lines
rust
trait A { fn m(&self) { println!("A"); } }
trait B { fn m(&self) { println!("B"); } }

struct S;
impl A for S {}
impl B for S {}      // fine - no error here

// S.m();            // error: multiple applicable items in scope
A::m(&S);            // "A" - resolved at the call site

go deeper

for a junior

Know that two conflicting defaults must be disambiguated by the implementer, and that Java uses Iface.super.m() to do it.

for a middle

Add the precedence rules — class wins, more specific interface wins — and be able to name a language that reports the conflict somewhere else.

for a senior

Reason about it as a library-evolution hazard, name where each language reports it, and prefer delegation over duplication when disambiguating.

for a principal

Set library policy on adding default bodies, treat it as potentially source-breaking, and understand that a polyglot API surface will fail in different places on different runtimes.

## The situation Once interfaces are allowed to carry method bodies, a type can acquire two bodies for the same signature from two different supertypes. Every language that permits interface defaults has had to invent a rule, and the rules differ in three dimensions: **when** the conflict is detected, **who** must write the disambiguation, and **what** the default winner is if there is one. ## Java: detected at the class, fixed by overriding Java's rule is deliberately conservative. If a class inherits two `default` methods with the same signature from unrelated interfaces, the class does not compile. There is no precedence to memorise and no silent winner; the compiler simply refuses and you must override the method in the class. Inside that override, `A.super.m()` names one specific inherited body — a syntax that exists only for this situation. Two secondary rules matter. A method inherited from a **class** beats any interface default ("class wins"), which keeps existing code stable when a new default appears. And a **more specific interface** beats a less specific one: if `B extends A` and both define `m`, `B`'s body wins with no error. The cost of detecting at the class is a source-compatibility hazard: an upstream library adding a default method can break a downstream class that already implements two interfaces, even though nothing in that class changed. ## C#: most specific wins, and defaults are not class members C# 8 default interface members follow a most-specific-override rule from the same family, and unrelated conflicts are likewise an error the implementer must resolve. The distinctive part is elsewhere: a default body is **not** part of the implementing class's member set. Given `class S : IA` where `IA` provides `M`, `new S().M()` does not compile; `((IA)new S()).M()` does. The practical consequence is that the object's behaviour depends on the *static type of the reference* used to call it, which is a different world from Java's guarantee that a default is just a normal virtual method. ## Rust: nothing is wrong until you call Rust traits may both provide `fn m(&self)` with default bodies, and a struct may implement both. That code compiles without complaint, because a trait impl is a separate namespace rather than a contribution to the type's own member list. The ambiguity surfaces at the **call site**: `s.m()` fails with "multiple applicable items in scope", and you write `A::m(&s)` or the fully-qualified `<S as A>::m(&s)` to pick one. This is a genuinely different placement of the same decision. Rust never forces a type to have one answer for `m`; it forces each *caller* to say which trait's `m` it meant. A library can therefore add a trait impl that would be a compile error in Java and it only affects call sites that use both traits in scope. ## Swift: static dispatch means the conflict may not be reported at all Swift protocol extensions provide bodies too, but only members that are **declared protocol requirements** get a witness-table entry and dynamic dispatch. Members that exist only in an extension are resolved on the declared type of the reference. So a value conforming to two protocols whose extensions both supply the same helper can invoke either, depending on how it is typed at the call site, and a conformer's own method of the same name may be skipped entirely when the value is held as a protocol type. The compiler complains only when it genuinely cannot pick. ## Scala's answer, for contrast Scala resolves conflicts by linearising the mixin order — the rightmost trait wins, and `super[T].m()` names a specific one. That mechanism belongs to the traits and mixins discussion; the point here is that it is a fourth answer to the same question, this time with a deterministic winner and no error at all. ## What this means in practice - **Never rely on "the compiler will catch it" being universal.** It is true in Java at the class, true in Rust at the call, and largely false in Swift for extension-only members. - **Treat adding a default body upstream as a potentially source-breaking change** in Java and C#, and document it as such in library release notes. - **Keep interface defaults thin and non-overlapping.** The common naming victims are generic verbs: `close`, `render`, `size`, `merge`, `validate`. Two independently designed interfaces converge on those names easily. - **When you must disambiguate, delegate rather than duplicate.** Overriding and calling one specific inherited body keeps a single implementation of the behaviour; copying the body into the class quietly forks it.

  • In Java, what happens if one of the two conflicting methods comes from a superclass rather than an interface?
    The class wins. An inherited concrete method from a superclass takes precedence over any interface default with the same signature, and no error is reported. This rule exists so that adding a default method to an interface cannot silently change the behaviour of classes that already inherit an implementation.
  • Why does Rust not force the type itself to pick a winner?
    Because a trait impl is not a contribution to the type's own member list; it is a separate mapping from a trait and type pair to bodies. Nothing about the struct is ambiguous — only a bare `s.m()` is, and only when both traits are in scope. Pushing the decision to the call site means adding an impl in a library affects strictly fewer places.
  • How should a library author reduce the chance of causing one of these conflicts?
    Keep default bodies rare and give operations specific names rather than generic verbs, since collisions cluster on names like close, render and validate. Where a helper is genuinely convenient, prefer a static or free function over a default member, and announce any new default in release notes as a possible source-level break for classes implementing several interfaces.

saying these in an interview costs you the question

  • Assuming every language errors at the class declaration; Rust errors at the call site and Swift may not error at all.
  • Believing a C# default interface member can be called on a class-typed reference.
  • Forgetting Java's 'class wins' rule and predicting an error when a superclass supplies the method.
  • Copying the inherited body into the disambiguating override instead of delegating, which forks the implementation.
  • Calling this multiple inheritance of state — none of these languages inherits fields through interfaces.

context