Interfaces & Type Identity
How interfaces are represented and dispatched, type assertions, typed nil, and the comparability of types.
20 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 has none, and a type satisfies an interface as soon as it has all of its methods. - ✗Thinking an interface lists fields a struct must have: it lists only methods, and any type with those methods 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?
JuniorTheoryCommonWhat does the program print?
What does the program print?
0 false
Open full question →Common mistakes
- ✗Expecting
2 true: a type assertion only checks the type stored inv; it never converts42into the string"42". - ✗Expecting a panic: only the one-result form
s := v.(string)panics; with, oka failed assertion setsoktofalseandsto"".
Follow-up questions
- →What does the program print if the assertion is changed to
v.(int)? - →How would a type switch on
vhandle bothintandstring?
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?
JuniorTheoryCommonWhat happens when this program is built and run?
What happens when this program is built and run?
It does not compile
Open full question →Common mistakes
- ✗Expecting
4: a type implements an interface only when each method has the same parameters and results, and thisAreareturnsint, notfloat64. - ✗Expecting a panic at run time: the compiler checks the methods when
Square{Side: 2}is assigned to aShapevariable, so the program never runs.
Follow-up questions
- →How would you change
Squareso that the program compiles and prints4? - →Why does Go check interface satisfaction at compile time rather than at the call?
JuniorTheoryCommonWhat does the program print?
What does the program print?
true false true
Open full question →Common mistakes
- ✗Expecting
true true true: an interface value isnilonly when both its type and its value are empty, andsalready holds the type*Rect. - ✗Expecting a panic at
s.(*Rect): the assertion succeeds because the dynamic type is*Rect, and it returns the nil pointer, so== nilistrue.
Follow-up questions
- →Why can a function that returns
errorhand back a non-nil error that holds a nil pointer? - →What would
s == nilprint right aftervar s any, before the assignment?
JuniorTheoryCommonWhat does the program print?
What does the program print?
{21} 21°C
Open full question →Common mistakes
- ✗Expecting
21°C 21°C:Stringhas a pointer receiver, so only*Tempimplementsfmt.Stringer, and the valuetprints as its plain fields. - ✗Expecting the pointer to print as
&{21}:&thas the type*Temp, which has theStringmethod, sofmtcalls it and prints21°C.
Follow-up questions
- →How would you change the method so that
fmt.Println(t)also prints21°C? - →Why can you call
t.String()on the variablet, yettdoes not implementfmt.Stringer?
JuniorTheoryCommonWhat does the program print?
What does the program print?
21°C 21
Open full question →Common mistakes
- ✗Expecting
21 21:Celsiushas aString() stringmethod, so it satisfiesfmt.Stringer, andfmt.Printlnprintstby calling that method. - ✗Expecting
21°Ctwice:float64(t)is a plainfloat64, which has noStringmethod, sofmtprints the bare number21.
Follow-up questions
- →Why does
Stringconvertctofloat64before passing it tofmt.Sprint? - →Would
fmt.Printlnstill callStringif the method had the receiver(c *Celsius)?
JuniorTheoryCommonWhat happens when this program is built and run?
What happens when this program is built and run?
It does not compile
Open full question →Common mistakes
- ✗Expecting
true:==works on a struct only when every field is comparable, and a slice field makes the whole struct incomparable. - ✗Expecting a panic at run time: the compiler checks the field types and rejects
a == b, so the program never runs.
Follow-up questions
- →How would you check that two
Teamvalues have the same members? - →Could
Teambe used as a key of amap? Why or why not?
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?
JuniorTheoryOccasionalWhat does the program print?
What does the program print?
false
Open full question →Common mistakes
- ✗Expecting
true: when acaselists more than one type,xkeeps the typeany, and an interface that holds the type*intis not equal tonil. - ✗Expecting a nil pointer to match no branch:
vholds the dynamic type*int, so it matches*intin the list, notnil.
Follow-up questions
- →What would the program print if the branch were
case *int:alone? - →Which branch would
var v anywith no assignment match, and what wouldx == nilprint there?
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?