The itab (interface table) serves as the core runtime structure enabling efficient interface dispatch in Go. When a concrete type is first asserted or assigned to a non-empty interface, the runtime constructs or retrieves an itab that pairs the concrete type with the interface type. This structure contains a cached hash for quick type comparison and a function pointer table mapping each interface method index to the concrete type's method implementation, ensuring O(1) lookup during subsequent calls.
A financial trading platform required a modular architecture where market data parsers (JSON, FIX, ProtoBuf) could be dynamically loaded as plugins. Each parser implemented a Processor interface with Parse() and Validate() methods. The system's dispatch engine received opaque interface{} references from the plugin loader, necessitating type assertions before processing millions of messages per second.
One approach considered was a registry of function pointers indexed by string identifiers, bypassing interface overhead entirely. This offered minimal dispatch latency but sacrificed compile-time type safety, requiring manual upkeep of function signatures and complicating the addition of new methods to the Processor contract. It also fragmented the codebase, as each method required separate registration logic rather than satisfying a cohesive interface.
Another alternative involved refactoring to use Go's generics, parameterizing the dispatcher with type constraints. While this eliminated interface boxing and provided static dispatch at compile time, it prevented runtime plugin loading—since generics are resolved at compile time—and significantly increased binary size due to monomorphization of the high-frequency dispatcher code for each parser type.
The chosen solution leveraged interface assertions with explicit pre-warming of the itab cache during plugin initialization. By asserting each loaded plugin to the Processor interface immediately after loading (before the hot path), the runtime populated the global itab table in advance. This ensured that the critical message processing loop encountered only cached itab lookups, combining the flexibility of dynamic loading with O(1) dispatch latency comparable to virtual table implementations in other languages.
The result was a system capable of handling over one million messages per second with sub-microsecond dispatch overhead, while maintaining clean separation between the core engine and third-party plugins. The itab caching mechanism effectively eliminated the dynamic lookup penalty after the initial warm-up phase.
Question: Why does assigning a nil concrete pointer to an interface create a non-nil interface value that can still cause a panic when methods are called?
Answer: This occurs because the interface header contains two words: the itab pointer (type information) and the data pointer (the value). When assigning a nil pointer of type *T to an interface, the data word is nil, but the itab word points to the valid type descriptor for *T. The interface itself is therefore non-nil and carries type information. When a method is invoked, the runtime uses the itab to find the method address and calls it with the nil receiver. Only if that method dereferences the receiver without a nil check does the panic occur, distinguishing this from a truly nil interface (where itab is nil) which panics immediately on method call.
Question: How does the runtime handle interface dispatch for types defined in separately compiled packages or dynamically loaded plugins?
Answer: The runtime maintains a global hash table of itabs keyed by the pair of (concrete type, interface type). When a new plugin is loaded or packages link, if a type assertion occurs for a combination not yet seen, the runtime calculates the itab by iterating over the interface's method list and finding corresponding methods in the concrete type's method set via name and signature hash matching. This newly constructed itab is then inserted into the global cache. Subsequent assertions across any goroutine use this cached itab, ensuring cross-package and dynamic plugin interface satisfaction operates with the same O(1) efficiency as intra-package calls.
Question: Can a single concrete type have multiple itab representations for the same interface due to different embedding or aliasing?
Answer: No, for a given pair of specific concrete type and specific interface type, there exists exactly one itab in the runtime. Go's type system canonicalizes type descriptors; even if a type is accessed via different import paths or aliases (e.g., mypkg.MyType vs other.MyType where one is an alias), they resolve to the same underlying type descriptor. Consequently, the runtime generates or looks up the same itab pointer for all assertions of that concrete type to that interface, ensuring consistent method dispatch and allowing pointer equality comparisons of itab fields to serve as reliable type identity checks within the runtime.