In a Go struct that embeds two types both declaring Draw, how is d.Draw() resolved?
answer
- count the levels you traverse
- shallowest wins, silently
- a tie is not resolved by order
- the declaration is legal, the use is not
- qualify it or declare it yourself
basics
~20 sGo takes the shallowest match: a name declared on the outer struct shadows a promoted one. When two embedded types tie at the same depth, the short selector is illegal and the compiler reports it as ambiguous where you use it.
solid answer
~50 sSelector resolution is by depth. A field or method declared directly on the outer struct is at depth zero and silently shadows anything promoted from an embedded type; a name one level in beats the same name two levels in. If two or more candidates sit at the same shallowest depth — the case when a struct embeds two types that both declare `Draw` — there is no unique answer, so the selector is simply not legal and the build fails with an ambiguous-selector error. Crucially the struct declaration itself is fine; the error appears only where `d.Draw()` is written, or where the type is checked against an interface that needs `Draw`. The fixes are to qualify the call — `d.Grid.Draw()` — or to declare `Draw` on the outer type, which at depth zero outranks both.
code
go · 17 linestype Grid struct{ Rows int }
func (g Grid) Draw() {}
type Border struct{ Rows int }
func (b Border) Draw() {}
type Pane struct {
Grid
Border
}
func render(p Pane) {
p.Draw() // compile error: ambiguous selector
p.Grid.Draw() // fine: says which embedded value
}go deeper
Remember that a field or method you declare on the struct itself wins over one with the same name coming from an embedded type, and that the hidden one is still reachable by naming the embedded field.
Be able to state the depth rule precisely, including that a tie at the shallowest depth makes the selector illegal rather than picking a winner, and name the fixes.
Diagnose the confusing symptoms in real code: a build break at a call site far from the struct, or a missing-method error against an interface whose real cause is a collision between two embedded types.
Treat a struct that embeds two third-party or cross-team types as a latent break: a method added upstream can collide later, so decide when a team embeds and when it uses named fields with forwarding.
## The rule in one sentence For a selector `x.f`, Go looks for `f` at the shallowest depth in the type where any `f` exists. If exactly one candidate sits at that depth, that is the one. If two or more do, the selector is illegal. Depth is just how many embedded fields you traverse: a name declared on the struct itself is depth 0, a name promoted from a directly embedded type is depth 1, one promoted through two levels of embedding is depth 2, and so on. ## Shadowing is silent and often what you want ```go type Grid struct { Title string } type Pane struct { Grid Title string // depth 0 wins } ``` `p.Title` is the `Pane` field. Nothing warns you, and nothing is wrong: this is the deliberate mechanism for replacing part of an embedded type's surface. The shadowed one has not vanished — `p.Grid.Title` reaches it, and that explicit form is how a shadowing method delegates to the thing it shadowed. The same rule applies to methods. Declaring `func (p Pane) Draw()` when the embedded `Grid` already has `Draw` means `p.Draw()` calls yours. Depth, not declaration order, decides; reordering the fields in the struct changes nothing. ## Ambiguity is a compile error, not a silent pick Embed two types that offer the same name at the same depth and Go refuses to guess: ```go type Grid struct{ Rows int } func (g Grid) Draw() {} type Border struct{ Rows int } func (b Border) Draw() {} type Pane struct { Grid Border } ``` `p.Draw()` and `p.Rows` are both ambiguous selectors and will not compile. Many languages resolve a collision like this by declaration order or by a linearisation rule; Go has no such rule and does not want one, so the engineer arriving from another language sees a build break where they expected a winner. ## The error appears at the use site, not the declaration This is the part that surprises people. Declaring `Pane` above is perfectly legal. You can construct it, assign it and pass it around. The compiler complains only: - where a `p.Draw()` or `p.Rows` selector is actually written, and - where `Pane` is checked against an interface requiring `Draw`, because the ambiguous name is not in `Pane`'s method set. The message there is the usual missing-method complaint, which is a confusing way to be told about a collision two levels away. So a library can ship a struct with a latent ambiguity and only break the caller who first reaches for that name. It also means adding a method to one embedded type can break a distant call site that was compiling yesterday. ## Three ways out 1. **Qualify the selector.** `p.Grid.Draw()` names the one you meant. Always available, and the right fix when the collision is genuine and rare. 2. **Declare the name on the outer type.** A `func (p Pane) Draw()` at depth 0 outranks both candidates and makes the short selector legal again; inside it you call `p.Grid.Draw()`, `p.Border.Draw()`, or both, in whatever order the type actually means. 3. **Stop embedding one of them.** Give it an ordinary field name — `border Border` — and reach it through that name. This is usually the honest answer when only one of the two was ever meant to be part of the type's surface. ## What does *not* work You cannot embed the same type twice to get two copies: `struct { Grid; Grid }` and `struct { Grid; *Grid }` are both rejected outright, because both embedded fields would be named `Grid`, and a struct may not declare a duplicate field name. That is one of the few embedding errors reported at the declaration itself. Also note ambiguity is per name, not per type. Two embedded types that collide on `Draw` still promote every other name they do not share; only the colliding selector is unusable. ## Why the design is like this The depth rule makes shadowing predictable — you can always tell which one wins by counting levels, without knowing declaration order or the whole type graph — and refusing ties keeps the reader from having to know a precedence rule to understand a call. The cost is a compile error the language could have papered over; the benefit is that no Go call site silently means something other than what it appears to.
- If two embedded types collide on Draw, does the outer struct still satisfy an interface that requires Draw?No. The ambiguous name is not in the outer type's method set, so the type fails the interface check with a missing-method error rather than an ambiguity message — a confusing symptom whose real cause is the collision. Declaring `Draw` on the outer type at depth zero fixes both the selector and the interface.
- Does the order of the embedded fields in the struct change which one wins?No. Resolution is purely by depth; among candidates at the same depth there is no tie-break at all, so swapping the two lines changes nothing. Order only matters for unrelated things such as memory layout and composite-literal positional forms.
- Can you embed the same type twice to hold two of them?No. Both embedded fields would take the same implicit name, and a struct cannot declare duplicate field names, so `struct{ Grid; Grid }` and `struct{ Grid; *Grid }` are both rejected at the declaration. Give at least one of them an ordinary field name instead.
- How does a method you declare on the outer type reach the one it shadows?Through the embedded field's implicit name: `func (p Pane) Draw() { p.Grid.Draw(); p.Border.Draw() }`. There is no implicit reference to the shadowed method, so you name the embedded value explicitly and call in whatever order the outer type actually means.
It is like two colleagues with the same first name in different rooms: someone in your own room answers first, but if both are equally far away nobody moves until you use a surname.
saying these in an interview costs you the question
- Says the first embedded field listed wins a tie
- Expects the compiler to flag the ambiguity at the struct declaration
- Thinks an ambiguous selector is a run-time panic
- Believes reordering the embedded fields changes resolution
- Claims a shadowed promoted name is no longer reachable at all
- Assumes embedding the same type twice gives two independent copies