Interfaces & Type Identity
How interfaces are represented and dispatched, type assertions, typed nil, and the comparability of types.
13 questions
JuniorTheoryVery commonWhat is an interface in Go and how is it satisfied?
What is an interface in Go and how is it satisfied?
An interface is a type defined by a method set. Any concrete type that has those methods satisfies the interface implicitly: there is no implements keyword and no explicit declaration. An interface value holds a (dynamic type, value) pair. The empty interface interface{} / any is satisfied by every type, and interfaces give Go its polymorphism and decoupling.
Common mistakes
- ✗Looking for an
implementskeyword — Go satisfies interfaces implicitly - ✗Thinking an interface stores only the value, not its dynamic type too
- ✗Believing
anyexcludes some types — every type satisfies it
Follow-up questions
- →How does a nil interface differ from an interface holding a nil pointer?
- →When does assigning to an interface allocate on the heap?
MiddleTheoryVery commonHow do type assertions and type switches work on interface values in Go?
How do type assertions and type switches work on interface values in Go?
A type assertion x.(T) checks the interface's dynamic type and extracts the concrete value; the single-result form panics on mismatch, while the comma-ok form v, ok := x.(T) reports failure via ok instead. A type switch switch v := x.(type) dispatches on the dynamic type across several cases at once.
Common mistakes
- ✗Using the single-result
x.(T)form on untrusted input and getting a panic instead of anokcheck - ✗Thinking a type assertion converts or reinterprets bits rather than checking the dynamic type
- ✗Believing a type switch falls through or runs multiple case bodies like a C
switch
Follow-up questions
- →What does
x.(T)return forvandokwhenxitself isnil? - →How does asserting to an interface type differ from asserting to a concrete type?
JuniorTheoryCommonWhich Go types are comparable and usable as map keys?
Which Go types are comparable and usable as map keys?
Comparable types support ==: booleans, numerics, strings, pointers, channels, interfaces, and structs/arrays whose every field is comparable. slices, maps, and functions are NOT comparable — only to nil — so cannot be map keys, which must be comparable.
Common mistakes
- ✗Thinking a
sliceormapcan be used as a map key — both fail to compile - ✗Believing struct
==always works regardless of its field types - ✗Expecting
slice == sliceto compare elements rather than fail to compile
Follow-up questions
- →How do you key a map by the contents of a slice?
- →Why can comparing two interfaces panic at runtime?
MiddleDebuggingCommonWhy does this cache always miss, so Get always returns nil?
Why does this cache always miss, so Get always returns nil?
Type mismatch. Set stores a value: s.cache.Put(wh.Id, *wh) dereferences the pointer, so the cached dynamic type is warehouse.Warehouse. But Get asserts a pointer: item.(*warehouse.Warehouse). That assertion never succeeds, so Get always falls through to return nil and the cache never hits. Fix: store and assert the same type — store wh, assert *warehouse.Warehouse.
Common mistakes
- ✗Overlooking that
*whdereferences the pointer, storing a value not a pointer - ✗Assuming a type assertion auto-converts between a value and a pointer type
- ✗Blaming eviction, key types, or a race instead of the value/pointer mismatch
Follow-up questions
- →Why does
item.(*warehouse.Warehouse)returnok == falserather than panicking? - →What would
item.(warehouse.Warehouse)return given the currentSet?
MiddleTheoryCommonWhere should you declare a Go interface — next to the implementation or the consumer?
Where should you declare a Go interface — next to the implementation or the consumer?
Declare the interface in the consumer package — where the value is used, not where it is implemented. Because Go satisfies interfaces implicitly (duck typing), the consumer defines exactly the small method set it needs. This decouples the consumer from concrete types and makes it trivial to swap in a mock in tests. Implementations need not import the interface.
Common mistakes
- ✗Putting interfaces beside the implementation as in Java/C# rather than the consumer
- ✗Defining one big interface instead of a small one tailored to the consumer
- ✗Thinking the implementing type must import or name the interface
Follow-up questions
- →How does consumer-side declaration make mocking in tests easier?
- →Why does a smaller interface (fewer methods) produce looser coupling?
MiddleTheoryCommonHow is an interface value represented in memory in Go (iface vs eface)?
How is an interface value represented in memory in Go (iface vs eface)?
An interface value is two words. For an interface with methods (iface) the first word points to an itab — pairing the concrete type with its method set — and the second to the data. For the empty interface any (eface) the first word is just a *_type descriptor, the second the data pointer.
Common mistakes
- ✗Thinking an interface is one word — it is two: a type/itab word and a data word
- ✗Believing
efacecarries anitab— onlyifacedoes;efaceholds a bare*_type - ✗Assuming the concrete value is stored inline in the interface rather than behind the data pointer
Follow-up questions
- →When does the data word point to a heap allocation versus storing a pointer directly?
- →How is an
itabbuilt and cached the first time a concrete type meets an interface?
MiddleCodeCommonWhy does calling hello() on a nil *gopher pointer succeed instead of panicking?
Why does calling hello() on a nil *gopher pointer succeed instead of panicking?
A pointer-receiver method is just a function taking the pointer as its first argument, so calling it on a nil pointer is legal as long as the body never dereferences that pointer. hello only prints a constant and never touches g.name, so it runs fine. Accessing g.name would panic with a nil dereference.
Common mistakes
- ✗Assuming any method call on a
nilpointer panics at the call site - ✗Thinking Go allocates a zero value for a
nilreceiver - ✗Forgetting the panic happens only when the body dereferences the
nilpointer (e.g.g.name)
Follow-up questions
- →What exact runtime error appears if
helloreadsg.nameon thenilreceiver? - →How do some standard types (like a
nil*Tree) deliberately rely on nil-receiver methods?
MiddleCodeCommonWhat do a == b and m[b] print for two equal point structs, and why?
What do a == b and m[b] print for two equal point structs, and why?
It prints true and p. Structs are comparable with == when every field is comparable (no slices/maps/funcs), comparing field by field. Because a == b, they hash to the same map key, so m[b] finds the value under a. If point held a slice field, both the == and the map-key use would fail to compile.
Common mistakes
- ✗Thinking
==on structs compares identity rather than field-by-field values - ✗Believing a struct with a slice or map field is still comparable — it fails to compile
- ✗Forgetting that two equal structs hash to the same map key
Follow-up questions
- →Which field types make a struct non-comparable, and what is the compile error?
- →How can you still use a struct with a slice field as a map key?
MiddleTheoryCommonWhy can an interface holding a nil pointer compare unequal to nil in Go?
Why can an interface holding a nil pointer compare unequal to nil in Go?
An interface equals nil only when both of its words are zero. Assigning a nil *T pointer sets the type word to *T while the data word stays nil — the interface is now a non-nil "typed nil". The classic trap is returning such a value from a function whose result type is an interface.
Common mistakes
- ✗Returning a concrete nil
*Tfrom a function with an interface return type and expecting== nilto hold - ✗Thinking only the data word matters for
nilequality — the type word must be zero too - ✗Assuming
fmtprinting<nil>proves the interface itself is nil-equal
Follow-up questions
- →How do you correctly return a nil interface from a function instead of a typed nil?
- →Does calling a method on a typed-nil interface panic, and when?
MiddleCodeOccasionalWhat does this program print, given the typed-nil interface and the two type assertions?
What does this program print, given the typed-nil interface and the two type assertions?
It prints i is nil then i value is nil — two lines. After i = t with t a nil *Type, the interface holds type word *Type and nil data, so i == nil is false and that branch is skipped. But i.(*Type) extracts the underlying pointer, which is nil, so that compares true. After t = &Type{} the pointer is non-nil, so the final branch is skipped.
Common mistakes
- ✗Expecting the second
i == nilto be true because the stored pointer is nil - ✗Thinking
i.(*Type)re-wraps a nil and so panics rather than yielding the nil pointer - ✗Confusing
i == nil(compares the whole interface) withi.(*Type) == nil(compares the extracted pointer)
Follow-up questions
- →Would the second branch print if
thad been declared asInterfaceinstead of*Type? - →What would
i.(*Type)do if the interface held a different concrete type at that point?
MiddleTheoryOccasionalWhat problem do generics solve in Go, and why were they wanted over interface{}?
What problem do generics solve in Go, and why were they wanted over interface{}?
Generics let you write one type-safe implementation that works over many types, checked at compile time. The old alternative — interface{} — loses static type safety: it needs runtime type assertions, boxes values (extra allocations), and pushes type errors to runtime. Generics also cut the copy-paste of writing the same function per concrete type.
Common mistakes
- ✗Believing
interface{}is type-safe rather than deferring all type checks to runtime - ✗Thinking generics add runtime cost like reflection, rather than being resolved at compile time
- ✗Assuming generics replace interfaces entirely instead of complementing them
Follow-up questions
- →How does a type constraint differ from an ordinary interface used as a parameter type?
- →What is type inference, and when must you still spell out the type argument explicitly?
SeniorTheoryRareWhat is the cost of an interface method call in Go, and what is the itab?
What is the cost of an interface method call in Go, and what is the itab?
An interface call indirects through the itab — a per (interface, concrete type) pair holding the concrete *_type plus a slice of function pointers for the method set. The call loads the target from that table and jumps: a small, predictable cost, not free, and not devirtualized in the general case.
Common mistakes
- ✗Claiming interface calls are always devirtualized to direct calls with zero overhead
- ✗Thinking the
itabis rebuilt on every call rather than constructed once and cached - ✗Believing dispatch scales with the program's total method count rather than being a fixed indirect jump
Follow-up questions
- →When can the Go compiler actually devirtualize an interface call to a direct one?
- →Why does an interface call typically defeat function inlining?
SeniorTheoryRareHow do type parameters and constraints work in Go generics?
How do type parameters and constraints work in Go generics?
Since Go 1.18 a function or type declares type parameters in square brackets, [T Constraint]. A constraint is an interface that may list a method set and/or a type union; comparable is a common one. The compiler instantiates generic code via GC-shape stenciling with dictionaries, not pure per-type monomorphization.
Common mistakes
- ✗Thinking generic constraints are limited to method sets and cannot express type unions
- ✗Believing every instantiation produces a fully separate monomorphized copy like C++ templates
- ✗Assuming generics box arguments into
anyand dispatch by reflection at runtime
Follow-up questions
- →What is a dictionary in Go's generics implementation, and what does it carry?
- →Why does the
comparableconstraint exist instead of just usingany?