Structs & Methods
Structs and methods in Go — declaring structs, methods, value vs pointer receivers, embedding as composition, and the constructor idiom.
6 questions
JuniorTheoryVery commonWhat is a struct in Go?
What is a struct in Go?
A struct is a composite type that groups named fields, each with its own type. It is a value type: assigning or passing it copies every field. Structs support embedding (composition), methods, and field tags. Its zero value has each field at that field's own zero value, and two structs are comparable with == when all their fields are comparable.
Common mistakes
- ✗Thinking a struct is a reference type — assigning or passing it copies every field
- ✗Believing a struct cannot have methods or embed other types
- ✗Assuming every struct is comparable with
==regardless of its field types
Follow-up questions
- →How does embedding differ from inheritance in other languages?
- →Which field types make a struct non-comparable, and why?
JuniorTheoryCommonHow does Go initialize a struct without constructors?
How does Go initialize a struct without constructors?
Go has no constructors. The idiom is a plain factory function, conventionally NewT(...), that takes the inputs, validates them, and returns an initialized *T or T. It is an ordinary function, not special syntax — nothing forces callers to use it. Often the struct's zero value is already usable, so no constructor is needed at all; you write NewT only when construction must validate inputs or set up non-zero defaults.
Common mistakes
- ✗Believing Go runs a
New/Initconstructor method automatically - ✗Thinking the zero value is never usable and always needs a factory
- ✗Assuming callers are forced to use
NewTrather than a plain literal
Follow-up questions
- →When is a struct's zero value usable enough to skip a constructor?
- →Why return
*Trather thanTfrom aNewTfactory function?
JuniorTheoryCommonWhat does struct embedding do in Go, and is it inheritance?
What does struct embedding do in Go, and is it inheritance?
Embedding puts a type as an unnamed field, like struct{ A }, which promotes A's fields and methods onto the outer struct so you can reach them directly. It is composition, NOT inheritance: there is no polymorphism and no virtual dispatch — the outer type is not a subtype of A. An own member with the same name shadows the embedded one, and the embedded value is still reachable explicitly through its type name, like outer.A.
Common mistakes
- ✗Calling embedding inheritance and expecting polymorphism or virtual dispatch
- ✗Assuming the outer type is a subtype assignable where the embedded type is wanted
- ✗Forgetting that an own member shadows the promoted embedded one
Follow-up questions
- →How do you still reach an embedded field after an own field shadows it?
- →If two embedded types promote the same method name, what happens?
JuniorTheoryCommonWhat is a method in Go and how does it differ from a function?
What is a method in Go and how does it differ from a function?
A method is a function with an extra receiver parameter written before the name, like func (c Counter) Inc(). The receiver binds the method to a named type — usually a struct — declared in the same package, and you call it as c.Inc(). A plain function has no receiver and is called by its bare name. So a method is just a function whose first argument is the receiver, with nicer call syntax and a method set on the type.
Common mistakes
- ✗Thinking the method body lives inside the struct's braces like a class
- ✗Believing you can attach a method to a type from another package
- ✗Forgetting that a method is a function with a receiver as first argument
Follow-up questions
- →Why must a method's receiver type be defined in the same package?
- →How does the receiver let you call a method as
c.Inc()instead ofInc(c)?
MiddleTheoryCommonWhen should a method use a pointer receiver instead of a value receiver?
When should a method use a pointer receiver instead of a value receiver?
Use a pointer receiver when the method must mutate the receiver, since a value receiver only gets a copy and the change is lost. Use one too when the struct is large, to avoid copying every field on each call, or for consistency when some methods on the type already need a pointer — mixing receiver kinds is discouraged. A value receiver fits small, immutable types where a copy is cheap and safe, and it also leaves the value usable concurrently.
Common mistakes
- ✗Using a value receiver for a method that must mutate the receiver
- ✗Copying a large struct on every call instead of taking a pointer
- ✗Mixing value and pointer receivers inconsistently across one type
Follow-up questions
- →Why is mixing pointer and value receivers on one type discouraged?
- →How does the receiver choice affect which method set satisfies an interface?
JuniorCodeOccasionalWhy does this method's mutation not persist, and how do you fix it?
Why does this method's mutation not persist, and how do you fix it?
It prints foo. A method call is sugar for passing the receiver as an argument, and Set has a value receiver, so b is a copy — b.Value = s mutates the copy and the original is untouched. Even passing &bs would still print foo, because Set copies the value regardless. Fix: make Set a pointer receiver, func (b *BasicStruct) Set(s string), and pass &bs.
Common mistakes
- ✗Thinking a value receiver mutates the caller's struct rather than a copy
- ✗Believing passing
&bsalone fixes it without changing the receiver - ✗Assuming the code fails to compile rather than just printing the unchanged value
Follow-up questions
- →Why is a value-receiver method in the method set of both
Tand*T? - →After switching
Setto a pointer receiver, why must you pass&bsnotbs?