Interfaces & Type Identity
Go did without classes: instead of inheritance it has interfaces, which a type satisfies implicitly just by having the right methods. Because of that simplicity the mechanics of an interface are barely studied — "well, it implements it and that's that." That is precisely why interfaces produce the quietest and most expensive bugs, the ones that do not fail at build time and surface only in production or in an interview.
There is structure under the simple syntax. An interface is not one word and not "a reference to an object" but a pair of machine words: a type word and a data word. That exact pair explains why an interface holding a nil pointer is not equal to nil, what a method call through it costs, and why comparing two interfaces sometimes compiles and sometimes panics. This topic takes type identity apart layer by layer — from the in-memory layout of an interface value to the rules for the comparability of structs and map keys.
Topic map
- Interface representation — an interface value as two words, the difference between
iface(with methods) andeface(the emptyany). - Interface dispatch — the indirect call through the
itab, its cost, the absence of devirtualization in the general case, and generics as a related mechanism. - Interface placement — declare the interface at the consumer; implicit satisfaction gives exactly the narrow method set needed.
- Type assertion — the
x.(T), comma-ok, and type-switch forms and their behaviour on a dynamic-type mismatch. - Typed nil — why an interface holding a nil pointer is not equal to
nil, and how this trap breaks error handling. - The nil receiver — a pointer-receiver method works on a
nilpointer as long as it never dereferences it. - Comparable types — which types support
==and work asmapkeys; slice, map, and func compare only tonil. - Struct comparison —
==compares structs field by field; equal ones hash to onemapkey, and a slice field breaks comparability.
Common Mistakes and Traps
| Mistake | Consequence |
|---|---|
| Treating an interface as one word or a reference to an object | You cannot explain typed nil or dispatch cost |
Thinking eface carries an itab | Only iface has one; eface holds a bare *_type |
| Believing an interface call is always devirtualized and free | Wrong cost model; in the general case it is an indirect jump |
| Declaring a wide interface "for later" at the type's provider | Needless coupling; a narrow interface at the consumer is correct |
Using the single-result x.(T) form on an untrusted value | A panic on a dynamic-type mismatch instead of an ok check |
Thinking a repeated x.(T) "locks in" the type on the interface | An assertion does not change the interface — it is only a check |
Returning a concrete nil *T from a function whose result type is error | The interface is not equal to nil — if err != nil fires falsely |
Believing a method call on a nil receiver always panics | It is legal as long as the body never dereferences the receiver |
Treating a zero string as equal to nil | A zero string is ""; s == nil does not even compile |
Writing to a nil map | A read gives the zero value, but a write panics assignment to entry in nil map |
| Comparing two interfaces with a non-comparable dynamic type | It compiles but panics at runtime comparing uncomparable type |
Putting a slice/map/func field in a struct and comparing it with == | Non-comparability is contagious — a == b does not compile, nor can it be a map key |
Interview Relevance
Interfaces are the main event of a Go interview, and what is asked is not the syntax but the mechanism under it: how an interface value is laid out in memory and what follows from that layout.
What interviewers check:
- How an interface value is laid out in memory — two words,
ifaceversuseface. - What an interface method call costs and what the
itabis. - Where to declare an interface and why — at the consumer, with a narrow method set.
- How
x.(T), comma-ok, and the type switch work and when the single-result form panics. - Why an interface holding a nil pointer is not equal to
nil— the central trap of the topic. - Why a method on a
nilreceiver sometimes works and sometimes panics. - Which types are comparable, work as
mapkeys, and why a slice field breaks a struct's comparability.
A typical wrong answer: "an interface with nil inside is of course equal to nil — it is nil, after all." That opens the discussion that an interface is two words, and a non-empty type word makes the value non-nil even when the data word is zero.