Skip to Content
Course content

182: Common Go Interview Questions on Struct Embedding

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you're prepping for a Go interview, someone is almost certainly going to ask you about struct embedding. Usually, they're trying to bait you into saying the word "inheritance." I've seen a lot of senior devs stumble here because they come from Java or C# and their brain instinctively goes to class hierarchies. But Go doesn't do inheritance. It does composition via embedding.

Let's actually play this out in the editor. I want to build a simple system for a smart home. We'll have a basic Device and a specific Light. I'll start by embedding the device into the light and see how the compiler handles it.

Wait, is this just inheritance?

package main

import "fmt"

type Device struct {
	Brand string
	ID    int
}

type Light struct {
	Device // Embedding
	Brightness int
}

func main() {
	l := Light{
		Device: Device{Brand: "Philips", ID: 123},
		Brightness: 80,
	}
	
	// Look at this: I'm accessing Brand directly on l
	fmt.Println("Brand:", l.Brand) 
}

Now, at first glance, l.Brand looks like Light inherited the field from Device. But that's a lie. What's actually happening is "promotion." Go sees that Light doesn't have a Brand field, so it looks into the embedded Device and promotes that field to the top level for convenience. It's just syntactic sugar. I could still write l.Device.Brand if I wanted to be explicit.

When things get messy: Shadowing

Interviewer's love to ask what happens when the outer struct and the embedded struct have the same field or method. Let's break my code on purpose to find out.

type Device struct {
	Brand string
}

func (d Device) PowerOn() {
	fmt.Println("Device is now on")
}

type Light struct {
	Device
	Brand string // Shadowing the embedded field
}

func (l Light) PowerOn() {
	fmt.Println("Light is now on")
}

func main() {
	l := Light{
		Device: Device{Brand: "Generic"},
		Brand: "Hue",
	}
	
	fmt.Println(l.Brand)   // Which one prints?
	l.PowerOn()            // Which one runs?
}

If you run this, l.Brand will be "Hue" and it'll print "Light is now on." The outer struct always wins. This is called shadowing. The embedded Device is still there, and its data is still intact, but it's hidden behind the Light's own definitions. If I ever need that original brand, I have to go deep: l.Device.Brand.

The "Who am I?" receiver problem

Here is the real killer question. This is where most candidates fail. I'm going to add a method to the Device that tries to use a field from the Light. Watch what happens.

type Device struct {
	Brand string
}

func (d Device) Describe() {
	// I want to print the Brand, and maybe the Brightness?
	// But wait... I'm a Device. I don't know what a Light is.
	fmt.Printf("Brand: %s\n", d.Brand)
}

type Light struct {
	Device
	Brightness int
}

func main() {
	l := Light{
		Device: Device{Brand: "Lutron"},
		Brightness: 100,
	}
	l.Describe()
}

The code above compiles and runs fine, but Describe() can only see d.Brand. It has absolutely no access to l.Brightness. Why? Because the receiver of Describe() is Device, not Light.

Even though I called l.Describe(), the method was promoted from Device. When that method executes, the receiver is just the embedded Device struct. It has no idea it's being embedded inside a Light. In a true inheritance language, the base class method could often be overridden or use polymorphism to see the child's state. In Go, the embedded struct is totally oblivious to its parent. It's a one-way street.




📋 Practical Task

Fixing the SmartHome Logger Shadowing Bug

You are reviewing a teammate's code for a smart home system. They tried to implement a Sensor that embeds a BaseComponent. However, they've encountered a bug: the LogStatus method is printing the wrong information because of field shadowing, and the BaseComponent method cannot access the Sensor's specific Reading value.

Your Task:

  1. Modify the Sensor struct and its methods so that calling s.LogStatus() prints the Name from the BaseComponent AND the current Reading from the Sensor.
  2. Ensure that you resolve the shadowing conflict where both BaseComponent and Sensor have a field named Status, making sure the Sensor's status is the one prioritized.
  3. Since BaseComponent.LogStatus cannot see Sensor fields, you must override LogStatus on the Sensor struct to achieve the desired output.
package main

import "fmt"

type BaseComponent struct {
	Name   string
	Status string
}

func (bc BaseComponent) LogStatus() {
	fmt.Printf("Component %s is %s\n", bc.Name, bc.Status)
}

type Sensor struct {
	BaseComponent
	Status  string
	Reading float64
}

func main() {
	s := Sensor{
		BaseComponent: BaseComponent{
			Name:   "Temperature Sensor",
			Status: "Initializing",
		},
		Status:  "Active",
		Reading: 22.5,
	}

	// This currently only prints "Component Temperature Sensor is Initializing"
	// because it's using the promoted BaseComponent method.
	// FIX THIS to print: "Sensor Temperature Sensor [Active] reads 22.5"
	s.LogStatus()
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.