Interface Internals: iface (with itab) vs eface (Empty Interface)
- Last updated
- Prerequisites:
- Go interface basics
- Understanding of static vs dynamic types
- go
- interfaces
- iface
- eface
- itab
- dynamic-dispatch
- type-assertion
Read at your depth
The practical view
An interface value in Go holds a concrete value plus its type information. The empty interface (interface{}) is represented as eface: { type, data } — just the type and the value pointer. A non-empty interface (e.g. io.Reader) is iface: { tab (itab), data } — where itab is a pointer to a table containing the interface type, the concrete type, and the function pointers for the interface methods implemented by the concrete type. Assigning a concrete value to an interface does an implicit conversion; calling a method through the interface is a dynamic dispatch through the itab. Interface equality compares type + data.
The same idea in other frameworks
N/A — Go runtime concern.N/A — Go runtime concern.N/A — Go runtime concern.N/A — Go runtime concern.Legacy vs modern
Concrete calls vs interface dispatch
Calling a method on a concrete type is a direct call; through an interface it's an indirect call via the itab — one more indirection, with an escape to the heap for non-pointer values.
var w io.Writer = bufio.NewWriter(f) // ❌ OLD: interface everywhere
for i := 0; i < n; i++ { w.Write(b) }w := bufio.NewWriter(f) // ✅ NEW: concrete type
for i := 0; i < n; i++ { w.Write(b) } // ✅ NEW: direct, inlinable callInterview gotchas
Context
This tests the runtime representation — interviewers want the two-word header layout and why interface{} is special-cased for efficiency.
The mechanical answer
eface is the empty interface (interface{} or any): a two-word header { type *rtype, data unsafe.Pointer }. It holds only the concrete type and the value — there are no methods to dispatch, so no method table is needed. iface is a non-empty interface: { tab *itab, data unsafe.Pointer }, where itab contains the interface type, the concrete type, a hash (for fast comparison), and a variable-length array of function pointers — the concrete type's implementations of the interface's methods. Method calls through a non-empty interface go through this table. The empty interface is cheaper (no itab), which is why interface{} is the universal 'any' container. The data word points to the value (pointer types) or to a heap-allocated copy (non-pointer values).
Trap
The naive answer is 'iface and eface are the same thing'. They differ by the presence of the itab/method table. Another trap: assuming interface conversion never allocates — a large non-pointer value assigned to an interface escapes to the heap for the data word. Candidates who describe the two-word layout and the itab contents show real Go internals knowledge.
Context
This probes the lazy itab construction — interviewers want the global itab cache and its keying, which explains the warm-up pattern in Go programs.
The mechanical answer
The first time a concrete type is converted to a given interface type, the runtime must build the itab: it walks the concrete type's method set, matches the interface's methods, and records the function pointers. This construction is non-trivial. The itab is then cached in a global hash table keyed by the pair (interface type, concrete type), so subsequent conversions of the same pair reuse the cached itab — a fast lookup. This is why benchmark code that converts once before the timed loop sees warm behavior: the itab is already built and cached. The cache also handles the negative case (a type that doesn't implement the interface) by caching a 'failed' entry, so the check is also fast on subsequent attempts.
Trap
The naive answer is 'interface conversion is always fast'. The first conversion pays the itab construction; only the cache makes repeats cheap. Another trap: assuming the itab lookup is per-call — it's per (interface, concrete) pair, cached globally, so a repeated conversion in a loop costs one hash lookup. Candidates who mention the global itab cache and its keying show real understanding.