Go/
Lesson

Ask AI to "create a user hierarchy with admin and regular users" in Go and watch what happens. It'll often generate Java-style inheritance patterns using embedding -- Admin embeds User embeds BaseEntity. This looks familiar but breaks Go's design philosophy. Embedding is composition ("has-a"), not inheritance ("is-a"), and misusing it creates bugs AI won't warn you about.

Struct embedding basics

Go lets you embed one struct inside another. The embedded struct's fields and methods get "promoted" to the outer struct:

type Person struct {
    Name string
    Age  int
}

func (p Person) Greet() string {
    return fmt.Sprintf("Hi, I'm %s", p.Name)
}

type Employee struct {
    Person      // Embedded -- no field name
    EmployeeID  int
    Department  string
}

func main() {
    emp := Employee{
        Person:     Person{Name: "Alice", Age: 30},
        EmployeeID: 12345,
        Department: "Engineering",
    }

    fmt.Println(emp.Name)     // "Alice" -- promoted from Person
    fmt.Println(emp.Greet())  // "Hi, I'm Alice" -- promoted method
}

No field name on Person -- just the type. That's what makes it embedding rather than a regular field.

02

Embedding vs regular fields

ApproachSyntaxField accessMethod access
Regular fieldp Personemp.p.Nameemp.p.Greet()
EmbeddingPersonemp.Nameemp.Greet()

Use embedding when you want the inner type's APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. to be part of the outer type's API. Use a regular field when you want to keep it private or just use it internally.

AI pitfall
AI embeds types too aggressively. Ask for "a client with logging" and it embeds the entire log.Logger, promoting every logging method onto your client type. If you don't want callers to see those methods, use a regular field instead:
> // AI generates this -- exposes all of http.Client's methods
> type MyClient struct {
>     http.Client
> }
>
> // Better -- only expose what you need
> type MyClient struct {
>     client http.Client
> }
>
> func (c *MyClient) Get(url string) (*http.Response, error) {
>     return c.client.Get(url)
> }
>
03

The critical difference: embedding is not inheritance

This is the concept AI gets wrong most often. In Java, Dog extends Animal means a Dog IS an Animal -- you can pass a Dog anywhere an Animal is expected. In Go, embedding doesn't create that relationship:

type Animal struct {
    Name string
}

func ProcessAnimal(a Animal) {
    fmt.Println("Processing:", a.Name)
}

type Dog struct {
    Animal
    Breed string
}

func main() {
    dog := Dog{Animal: Animal{Name: "Rex"}, Breed: "Shepherd"}

    // This does NOT work
    // ProcessAnimal(dog)  // Compile error: cannot use Dog as Animal

    // You must explicitly access the embedded struct
    ProcessAnimal(dog.Animal)  // Works
}
AI pitfall
AI generates code that treats embedded types as parent classes. It will create a function accepting Animal and then pass a Dog, expecting Go inheritance that doesn't exist. When you see this pattern, the fix is usually to define an interface instead:
> type Named interface {
>     GetName() string
> }
>
> func (a Animal) GetName() string { return a.Name }
> // Dog inherits GetName through embedding -- and satisfies Named
>
> func Process(n Named) {
>     fmt.Println("Processing:", n.GetName())
> }
>
04

Method promotion and overriding

Embedded methods are promoted, and the outer type can override them:

type Vehicle struct {
    Make string
}

func (v Vehicle) Start() string {
    return fmt.Sprintf("%s is starting", v.Make)
}

type Car struct {
    Vehicle
    Model string
}

func (c Car) Start() string {
    return fmt.Sprintf("%s %s vroom!", c.Make, c.Model)
}

car := Car{
    Vehicle: Vehicle{Make: "Toyota"},
    Model:   "Camry",
}

fmt.Println(car.Start())          // "Toyota Camry vroom!" -- outer wins
fmt.Println(car.Vehicle.Start())  // "Toyota is starting" -- still accessible
Edge case
name collisions**
If both the outer and embedded struct have a field with the same name, the outer one wins. Access the embedded version by qualifying it:
> type Employee struct {
>     Person
>     Name string  // Shadows Person.Name
> }
> emp.Name         // Employee's Name
> emp.Person.Name  // Person's Name
>
05

Interface embedding

Interfaces can embed other interfaces to compose contracts:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

This is exactly how io.ReadWriter is defined in the standard libraryWhat is standard library?A collection of ready-made tools that come built into a language - no install required. Covers common tasks like reading files or making web requests.. Any type satisfying both Reader and Writer automatically satisfies ReadWriter.

06

Real-world embedding patterns

Thread-safe structs with sync.MutexWhat is mutex?A mutual exclusion lock that prevents concurrent access to shared data - only one thread or goroutine can hold it at a time.

The most common and idiomatic embedding pattern:

type SafeCounter struct {
    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.Lock()         // Promoted from sync.Mutex
    defer c.Unlock()
    c.count++
}

func (c *SafeCounter) Value() int {
    c.Lock()
    defer c.Unlock()
    return c.count
}
AI pitfall
AI sometimes generates this pattern with a value receiver, which copies the mutex -- creating a race condition that the race detector will catch but code review might miss:
> // WRONG -- value receiver copies the mutex
> func (c SafeCounter) Value() int {
>     c.Lock()   // Locking a COPY of the mutex
>     defer c.Unlock()
>     return c.count
> }
>

Always use pointer receivers on types that embed sync.Mutex.

Composable service dependencies

type Logger struct {
    prefix string
}

func (l Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

type Metrics struct {
    counters map[string]int
}

func (m *Metrics) Increment(name string) {
    m.counters[name]++
}

type Service struct {
    Logger
    *Metrics
    Name string
}

func (s *Service) DoWork() {
    s.Log("Starting work...")     // From Logger
    s.Increment("work_started")  // From Metrics
}
07

Evaluating AI's OOP translations

When AI translates OOP patterns into Go, use this table to evaluate:

OOP patternAI's Go translationCorrect Go approach
class Dog extends AnimalEmbed Animal in DogDefine interfaces for shared behavior
abstract class ShapeEmbed a BaseShape structDefine a Shape interface
interface ISerializableCreate Serializable interfaceCorrect, but drop the I prefix
Deep hierarchy (3+ levels)Nested embeddingFlatten with interfaces + composition
super.Method()d.Animal.Method()Correct, but question if needed
Multiple inheritanceEmbed multiple structsUsually correct, but watch for name collisions
08

Composition vs inheritance: the mental model

AspectInheritance (Java/Python)Composition (Go)
Relationship"is-a" (Dog is an Animal)"has-a" (Car has an Engine)
FlexibilityRigid hierarchyMix and match freely
Multiple inheritanceDiamond problemEmbed multiple structs safely
Method resolutionComplex chain up hierarchyOuter type wins, explicit access to inner
TestingMock entire class treeMock individual embedded types
PolymorphismThrough class hierarchyThrough interfaces (separate concept)
09

Quick reference

PatternSyntaxUse case
Struct embeddingType (no field name)Reuse fields and methods
Interface embeddingInterface inside interfaceCompose contracts
Access promoted fieldouter.FieldWhen no name collision
Access shadowed fieldouter.EmbeddedType.FieldWhen names collide
Override methodDefine same method on outer typeCustomize embedded behavior