Language Internals
Part 3 of 11 · Go Language ProficiencySyntax, Types, Structs & Methods
Syntax, types, structs, methods, embedding, and zero values.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
How do you declare a variable?
Answer
var with a type, or colon-equals inside a function when the type is obvious.
L2
What is the zero value of a struct?
Answer
Every type has a zero value (0, false, "", nil for pointers, slices, maps, channels, funcs, interfaces); not all are usable - writing to a nil map panics.
L3
When do you use a pointer receiver?
Answer
When the method mutates the receiver, or the struct is large enough that copying it is the wrong default.
L4
What is in the method set of T versus pointer-to-T?
Answer
The value method set has value-receiver methods only. The pointer method set also has pointer-receiver methods.
L5
Why does that matter for interfaces?
Answer
A value stored in an interface cannot call pointer methods. Store a pointer if the interface needs mutation.
L6
What does embedding promote?
Answer
Fields and methods of the inner type. Callers see them on the outer type. It is composition, not a subclass.
L7
How is that different from a Python subclass?
Answer
There is no method resolution order and no super. If you need a different behavior, write a method on the outer type.
Failure modes
Mutation on a value receiver
Inc copies the struct, increments the copy, and the caller sees the old value.
Interface holds T
The interface requires a pointer method, and a value does not satisfy it.
Embedding treated as extends
Callers assume overridden methods and a subtype relationship that Go does not create.
Constructor required by habit
A New function hides a perfectly usable zero value and forces nil checks that were unnecessary.
Misconceptions
Go objects need new or a constructor.
var c Counter is a live zero value. Constructors are optional helpers.
The receiver is passed by magic like this.
You write the receiver. Value or pointer is part of the method.
Embedding is inheritance.
It promotes names. The outer type is not a subtype of the inner type.
A pointer method is visible on both T and pointer-to-T in an interface.
Only pointer-to-T includes pointer methods.
Interviewer traps
Saying both receivers mutate.
A value receiver mutates the copy. Show a field that stays unchanged.
Drawing a class diagram for embedding.
Name the promoted method and say there is no subtype.
Requiring a constructor before use.
State the zero value and when a constructor still helps, such as a non-nil map.
Design scenario
Same prompt for every reader.
Requirements
Pointer receiver for increment, value receiver for read, embedding for the logger, no class hierarchy.
Traffic / scale
One process, many requests calling Inc.
Latency
A value receiver that copies a large struct on every call shows up before the network does.
Consistency
Inc must change the same counter the handler reads.
Availability
A zero-value counter must be safe to increment.
Failure assumptions
- Inc uses a value receiver and the count stays 0.
- Server subclasses Logger.
Constraints
- Stay on structs and methods. Do not introduce channels.
- Say which method set the handler interface requires.
Prompt
Model a small in-memory counter and a server that logs through an embedded logger.
Receivers versus this
Prefer
Explicit receiver
You choose copy or pointer. The method set follows.
- Zero values are real.
- Pointer methods mutate.
- Embedding promotes names.
Alternative
Class and constructor
TypeScript and Python bind this or self and usually start in a constructor.
- Mutation is the default.
- extends builds a subtype.
- Uninitialized fields are a separate null story.
Overview
Bootstrap the syntax a TypeScript or Python reader trips on: declarations, zero values, structs, methods, and embedding. There is no class keyword.
| Idea | TypeScript | Python | Go |
|---|---|---|---|
| Variable | let or const | name equals | var or colon-equals |
| Data plus behavior | class | class | struct plus methods |
| Reuse | extends | subclass | embedding |
| Receiver | this | self | (c Counter) or (c pointer Counter) |
| Missing | null or undefined | None | nil and zero values |
Decisions
- 1
Declare a type
- nextNeed mutation?
- ?
Need mutation?
- yesPointer receiver
- noValue receiver
- 3
Pointer receiver
- nextEmbed for reuse
- 4
Value receiver
- nextEmbed for reuse
- 5
Embed for reuse
Lesson map
Syntax, Types, Structs & Methods
Syntax, types, structs, methods, embedding, and zero values.
Architecture. Declare a type Ready. Need mutation? Ready. Pointer receiver Ready. Value receiver Ready. Embed for reuse Ready
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB Decl["Declare a type Ready"] Mut["Need mutation? Ready"] Ptr["Pointer receiver Ready"] Val["Value receiver Ready"] Embed["Embed for reuse Ready"] Decl -->|continues| Mut Mut -->|yes| Ptr Mut -->|no| Val Val -->|continues| Embed Ptr -->|continues| Embed
Press Run. Snippets must be self-contained — no network, files, or native modules.
The Python class mutates self. The Go pointer receiver is the explicit version of that. A Go value receiver would not.
Rosetta — struct and methods
package counter
type Counter struct {
n int
}
func (c Counter) Value() int { return c.n }
func (c *Counter) Inc() { c.n++ }
func Demo() {
var c Counter
c.Inc()
_ = c.Value()
}class Counter {
constructor(private n = 0) {}
value(): number { return this.n; }
inc(): void { this.n++; }
}
const c = new Counter();
c.inc();class Counter:
def __init__(self, n: int = 0) -> None:
self._n = n
def value(self) -> int:
return self._n
def inc(self) -> None:
self._n += 1
c = Counter()
c.inc()var c Counter is already usable. Inc has a pointer receiver, so the method set that includes Inc is *Counter. Value is on both.
Rosetta — embedding
package server
type Logger struct{}
func (Logger) Log(msg string) {}
type Server struct {
Logger
Addr string
}
func Demo() {
s := Server{Addr: ":8080"}
s.Log("listening")
}class Logger {
log(msg: string) { console.log(msg); }
}
class Server {
constructor(public addr: string, private logger = new Logger()) {}
log(msg: string) { this.logger.log(msg); }
}class Logger:
def log(self, msg: str) -> None:
print(msg)
class Server:
def __init__(self, addr: str) -> None:
self.addr = addr
self._logger = Logger()
def log(self, msg: str) -> None:
self._logger.log(msg)Go promotes Log onto Server. TypeScript and Python delegate, or they inherit. Promotion is not a subtype. Server does not implement a hypothetical Logger interface unless its own method set matches.
Interview Q&A
Value or pointer receiver for an interface?
Answer
If the method mutates, the interface is satisfied by the pointer. A plain value does not include pointer methods in its method set.
What is a zero value?
Answer
Every type has a zero value (0, false, "", nil for pointers, slices, maps, channels, funcs, interfaces); not all are usable - writing to a nil map panics.
Does embedding override methods?
Answer
You can declare the same name on the outer type and that method wins for callers of the outer type. There is still no subclass.
Why can c.Inc() work on a value variable?
Answer
The compiler takes the address when the variable is addressable. That convenience disappears once the value is stored in an interface.
Is colon-equals allowed at package level?
Answer
No. Short declarations belong inside functions. Package scope uses var or a const.
How does Python self differ?
Answer
self is always the instance. You do not choose a copy receiver. A Go value receiver is that missing choice.
How does TypeScript this differ?
Answer
this depends on the call site unless the function is an arrow. A Go receiver is a declared parameter, not a call-site rule.
Which Go version is this syntax?
Answer
It is the stable language through Go 1.27.1. Generic methods are a separate page. Receivers themselves did not change shape.
Pitfalls
- Incrementing a copy and printing the original.
- Putting a value into an interface that needs pointer methods.
- Calling a promoted method and assuming a subclass relationship.
- Forgetting that a nil map is a readable zero and a bad write.
Write Inc twice, once with a value receiver and once with a pointer. Say which call leaves the caller at zero.