Under The Hood
backend go Go 1.21+

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

react equivalent
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.

before → after
Interface in a hot loop (unnecessary)
var w io.Writer = bufio.NewWriter(f) // ❌ OLD: interface everywhere
for i := 0; i < n; i++ { w.Write(b) }
Concrete type in the hot path
w := bufio.NewWriter(f) // ✅ NEW: concrete type
for i := 0; i < n; i++ { w.Write(b) } // ✅ NEW: direct, inlinable call

Interview 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.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.