DevPrep
  • Interview Prep
  • Projects
  • Resources
  • Pricing
  • About Us
Submit Question
DevPrep
  • Pricing
  • About Us
Submit Question

Practice

  • JavaScript
  • DSA
  • Machine Coding
  • System Design

Resources

  • Learning Tracks
  • Articles
  • Roadmaps
  • Compare Concepts
  • Glossary
  • Developer Tools
  • All Questions

Company

  • About
  • Pricing

Legal

  • Privacy Policy
  • Terms of Service
DevPrep

© 2026 DevPrep. All rights reserved.

← Back to Questions
HardTheory

Go Interface Internals: iface vs eface and the Nil Trap

14 views

Problem Statement

A production bug at Cloudflare caused a nil pointer panic despite a nil check passing. The root cause was Go's interface implementation. Explain the internal structure of interfaces and the infamous "nil interface" trap.

Interface Internal Structure

// Empty interface (interface{} or any)
type eface struct {
    _type *_type  // Type information
    data  unsafe.Pointer  // Pointer to actual value
}

// Non-empty interface (has methods)
type iface struct {
    tab  *itab           // Type + method table
    data unsafe.Pointer  // Pointer to actual value
}

The Nil Interface Trap

package main

import "fmt"

type MyError struct {
    msg string
}

func (e *MyError) Error() string {
    return e.msg
}

func mayFail(fail bool) error {
    var err *MyError = nil
    
    if fail {
        err = &MyError{"something went wrong"}
    }
    
    return err // ⚠️ Returns non-nil interface with nil data!
}

func main() {
    err := mayFail(false)
    
    // This check PASSES even though err.data is nil
    if err != nil {
        fmt.Println("Error:", err.Error()) // PANIC: nil pointer
    }
}

Why This Happens


When mayFail returns:

err (interface) = {
    type: *MyError    ← NOT nil!
    data: nil         ← The actual value is nil
}

An interface is only nil when BOTH type and data are nil.

The Fix

func mayFail(fail bool) error {
    if fail {
        return &MyError{"something went wrong"}
    }
    return nil // Return untyped nil
}

// Or check explicitly:
func isNilInterface(i interface{}) bool {
    if i == nil {
        return true
    }
    switch v := reflect.ValueOf(i); v.Kind() {
    case reflect.Ptr, reflect.Map, reflect.Slice, 
         reflect.Chan, reflect.Func, reflect.Interface:
        return v.IsNil()
    }
    return false
}

Interface Conversion Cost

// ❌ Expensive: []T to []interface{} requires copying
func toInterfaceSlice(s []int) []interface{} {
    result := make([]interface{}, len(s))
    for i, v := range s {
        result[i] = v // Each element boxed individually
    }
    return result
}

// The memory layout is different:
// []int:        [1][2][3][4][5] (contiguous ints)
// []interface{}: [iface][iface][iface] (array of fat pointers)

Type Assertions vs Type Switches

func process(v interface{}) {
    // Type assertion (single type)
    if s, ok := v.(string); ok {
        fmt.Println("String:", s)
    }
    
    // Type switch (multiple types)
    switch x := v.(type) {
    case int:
        fmt.Println("Int:", x*2)
    case string:
        fmt.Println("String:", x)
    case []byte:
        fmt.Println("Bytes:", len(x))
    default:
        fmt.Printf("Unknown: %T\n", x)
    }
}

Follow-up Questions

  1. What's the overhead of an interface method call vs direct call?
  2. Can a value receiver satisfy an interface expecting a pointer receiver?
  3. How does Go determine interface satisfaction at compile time?

Sample Test Cases

Case 1
Input
var i interface{} = 42
Expected Output
eface: type=int, data=42
Case 2
Input
var w io.Writer = &buf
Expected Output
iface: itab=(io.Writer,*bytes.Buffer), data=&buf

No solutions yet

Be the first to share a solution for this question.

Comments (0)

Sign in to leave a comment.

No comments yet. Be the first to comment.

Stats

Views
14
Likes
0
Solutions
0
Comments
0

Category

Backend Engineering