---
title: How Go Implements Interfaces Internally (itab)
summary: Go uses an `itab` structure to map interface methods to concrete type implementations for dynamic dispatch.
lang: en
slug: how-go-implements-interfaces-internally-itab
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.gofaq.org/en/how-go-implements-interfaces-internally-itab/
---

# How Go Implements Interfaces Internally (itab)



## The mystery of the missing vtable

You assign a concrete struct to an interface variable. You call a method on that variable. Go runs the correct code. Your struct has no hidden pointer to a method table. The struct is just data. Go does not use a C++ vtable embedded in the object. The magic lives in the interface value itself, not in the data.

Go resolves interface calls using a runtime structure called `itab`. The name stands for interface table. It maps the interface's method set to the concrete type's function pointers. The interface value holds a pointer to the `itab` and a pointer to the data. When you call a method, the runtime looks up the function pointer in the `itab` and jumps to it.

## How the interface value is built

An interface value in Go is a two-word structure. One word points to the `itab`. The other word points to the concrete data. The `itab` is shared across all interface values of the same type-interface pair. The runtime caches `itab` structures to avoid rebuilding them.

Think of the `itab` as a translation sheet. The interface defines a set of buttons. The concrete type defines the actions. The `itab` maps each button to the correct action for that type. When you assign a type to an interface, the runtime checks if a translation sheet exists. If it does, the runtime reuses it. If not, the runtime creates one and stores it for later.

The interface value carries the translation sheet and the device. The device holds the state. The sheet tells you how to operate the device. This separation keeps concrete types lightweight. You can pass a struct by value into an interface without bloating the struct with metadata.

```go
package main

import "fmt"

// Greeter defines a behavior.
type Greeter interface {
    Greet() string
}

// Person implements Greeter.
// Receiver name is short, matching the type.
type Person struct {
    Name string
}

// Greet returns a greeting.
// Method signature matches the interface.
func (p Person) Greet() string {
    return "Hello, " + p.Name
}

func main() {
    // Create concrete value on stack.
    p := Person{Name: "Alice"}

    // Assign to interface.
    // Runtime creates iface with itab pointer and data pointer.
    var g Greeter = p

    // Call method via interface.
    // Runtime uses itab to find Greet function pointer.
    fmt.Println(g.Greet())
}
```

The `itab` structure contains pointers to the interface type definition, the concrete type definition, a hash for equality checks, a flag indicating validity, and an array of function pointers. The method pointers follow the fields in memory. The runtime uses the method index to jump to the correct pointer.

Go interfaces are implicit. You do not declare that a type implements an interface. The compiler checks the method set at compile time. If the methods match, the assignment is allowed. This convention keeps code decoupled. Types do not need to know about the interfaces they satisfy.

**The interface value carries the map. The data carries the state.**

## Inside the runtime assignment

When you assign a concrete value to an interface, the runtime performs a lookup. It checks a global cache keyed by the interface type and the concrete type. If the cache hits, the runtime copies the cached `itab` pointer into the interface value. The data pointer points to the concrete value. The assignment is fast.

If the cache misses, the runtime allocates a new `itab`. It fills in the interface type pointer and the concrete type pointer. It computes a hash for the pair. It iterates over the interface's methods and finds the corresponding function pointers in the concrete type. It stores those pointers in the `itab`'s method array. It sets the validity flag. It inserts the `itab` into the cache. The runtime then creates the interface value with the new `itab` pointer.

The `bad` flag in the `itab` indicates a mismatch. If the concrete type does not implement the interface, the `bad` flag is set. The runtime checks this flag during assignment. If the flag is true, the assignment fails. The compiler usually catches mismatches early, but the runtime check protects against dynamic scenarios.

The `link` field in the `itab` connects entries in a linked list. This list is used for iteration during debugging or reflection. It allows the runtime to enumerate all `itab` structures for a given interface or type. You rarely interact with this field directly.

Method calls through an interface involve indirection. The runtime reads the `itab` pointer from the interface value. It reads the function pointer from the `itab` at the method index. It calls the function, passing the data pointer as the receiver. This indirection adds a small overhead compared to direct calls. The overhead is usually negligible, but it matters in tight loops.

**Caching makes repeated assignments cheap. The first assignment pays the cost.**

## Realistic usage and performance

In real code, interfaces appear in function parameters, return values, and slices. Passing a concrete type to a function that accepts an interface triggers the assignment logic. The runtime builds the interface value. If the `itab` is cached, the cost is minimal. The interface value is typically allocated on the stack.

Consider a function that processes any reader. The function accepts an `io.Reader` interface. You pass a `*os.File`. The runtime creates an interface value. The `itab` maps `Read` to `(*os.File).Read`. The data pointer points to the file. Inside the function, calls to `Read` go through the `itab`.

```go
package main

import (
    "fmt"
    "io"
    "strings"
)

// CopyBytes reads from src and writes to dst.
// Accepts interfaces to work with any reader/writer.
func CopyBytes(src io.Reader, dst io.Writer) error {
    // Buffer for reading.
    buf := make([]byte, 1024)

    for {
        // Read via interface.
        // Runtime uses itab to call concrete Read method.
        n, err := src.Read(buf)
        if n > 0 {
            // Write via interface.
            // Runtime uses itab to call concrete Write method.
            _, writeErr := dst.Write(buf[:n])
            if writeErr != nil {
                return writeErr
            }
        }
        if err != nil {
            // io.EOF is expected at end.
            if err == io.EOF {
                return nil
            }
            return err
        }
    }
}

func main() {
    // StringReader implements io.Reader.
    src := strings.NewReader("Hello, interfaces!")

    // os.Stdout implements io.Writer.
    dst := fmt.Println

    // Pass concrete types to function.
    // Runtime boxes them into interface values.
    err := CopyBytes(src, dst)
    if err != nil {
        fmt.Println("Error:", err)
    }
}
```

The `CopyBytes` function accepts `io.Reader` and `io.Writer`. You can pass a file, a buffer, a network connection, or a custom type. The function does not care about the concrete type. It relies on the interface contract. The `itab` ensures the correct methods are called.

Performance matters when you assign to interfaces in a tight loop. Each assignment creates an interface value. If the loop runs millions of times, the allocation and indirection add up. You can avoid the cost by keeping data concrete until the boundary. Pass concrete types internally. Convert to interfaces only when crossing abstraction boundaries.

**Accept interfaces at boundaries. Keep concrete types inside.**

## Pitfalls and runtime errors

The most common pitfall is the nil interface versus nil pointer. An interface value is nil only when both the `itab` pointer and the data pointer are nil. If you assign a nil pointer to an interface, the interface is not nil. It has a valid `itab` but a nil data pointer.

```go
package main

import "fmt"

type Speaker interface {
    Speak()
}

type Dog struct{}

func (d Dog) Speak() {
    fmt.Println("Woof")
}

func main() {
    // Nil pointer of concrete type.
    var d *Dog = nil

    // Assign nil pointer to interface.
    // Interface has itab but nil data.
    var s Speaker = d

    // Interface is not nil.
    if s == nil {
        fmt.Println("s is nil")
    } else {
        fmt.Println("s is not nil")
    }

    // Calling method panics.
    // Receiver is nil pointer.
    s.Speak()
}
```

The output shows `s is not nil`. The interface holds the `itab` for `*Dog`. The data pointer is nil. Calling `Speak` passes the nil pointer as the receiver. The method dereferences the receiver and panics. The runtime reports `panic: runtime error: invalid memory address or nil pointer dereference`.

To check for nil, you must check the concrete pointer, not the interface. Use a type assertion to recover the pointer. Check if the pointer is nil. Or check the interface type using reflection. The idiomatic approach is to avoid assigning nil pointers to interfaces. Return nil interfaces when the value is absent.

Another pitfall is method set mismatches. The compiler checks method sets at compile time. If a type does not implement an interface, the compiler rejects the code. The error message is clear. The compiler reports `Person does not implement Greeter (missing Greet method)`. You must add the missing method or change the interface.

Dynamic mismatches can occur with reflection or generated code. The runtime checks the `bad` flag in the `itab`. If the flag is set, the assignment fails. The runtime panics with a type assertion error. This protects against invalid conversions.

**A nil pointer inside an interface is not a nil interface. Check the pointer, not the interface.**

## When to use interfaces and alternatives

Go provides several ways to handle polymorphism and abstraction. Interfaces are the standard tool, but they are not always the best choice. Generics offer type safety without boxing. Concrete types offer performance. Type assertions recover concrete types. Choose the right tool for the job.

Use an interface when you need polymorphism across unrelated types. Interfaces let you define a behavior that multiple types can satisfy. The types do not need a common ancestor. This keeps code flexible and decoupled.

Use a concrete type when the caller knows the exact type and you want to avoid the overhead of dynamic dispatch. Concrete types are faster. They allow direct method calls and field access. Use concrete types for internal logic and performance-critical paths.

Use generics when you need type safety without the runtime cost of interface boxing. Generics let you write functions that work with multiple types while preserving the concrete type. The compiler generates specialized code for each type. This avoids indirection and allocation.

Use type assertions when you receive an interface and need to access fields or methods not defined in the interface. Type assertions recover the concrete type. Use them sparingly. They indicate a design smell if overused. Prefer adding methods to the interface instead.

**Pick the simplest abstraction that solves the problem. Interfaces are powerful. Generics are precise. Concrete types are fast.**

## Where to go next

- [How Interfaces Are Represented Internally in Go (iface and eface)](/en/how-interfaces-are-represented-internally-in-go-iface-and-eface/)
- [How to Define Custom Types with type in Go](/en/how-to-define-custom-types-with-type-in-go/)
- [Go Generics Best Practices and Common Patterns](/en/go-generics-best-practices-and-common-patterns/)