Go

What are the runtime representations of Go's built-in types? What does this print on 64-bit?

Question 365HardGo 1.22 to 1.25
type Stringer interface{ String() string }

func main() {
    var (
        s  string
        sl []int
        m  map[string]int
        ch chan int
        e  any
        i  Stringer
        f  func()
        p  *int
    )
    fmt.Println(unsafe.Sizeof(s), unsafe.Sizeof(sl), unsafe.Sizeof(m), unsafe.Sizeof(ch))
    fmt.Println(unsafe.Sizeof(e), unsafe.Sizeof(i), unsafe.Sizeof(f), unsafe.Sizeof(p))

    var ip *int
    e = ip
    fmt.Println(e == nil, ip == nil)
}

Output:

16 24 8 8
16 16 8 8
false true
  • string: a data pointer and a length, 16 bytes. Assigning a string copies only this header.
  • slice: pointer, len and cap, 24 bytes. That is why append results must be reassigned: the callee's copy of the header changes, not yours.
  • map and chan: a single pointer to a runtime struct (the map header, or hchan). That is why they behave like references when passed to functions, and why a nil map can be read but not written.
  • Empty interface (eface): a type pointer plus a data pointer. Non-empty interface (iface): an itab pointer (the type plus its method table for this interface, cached globally) plus a data pointer. Both are 16 bytes.
  • func value: a pointer to a closure object whose first word is the code pointer, followed by the captured variables.

The last line is the classic nil interface trap: e holds type *int with a nil data pointer, so its type word is set and it is not equal to nil. Returning a typed nil pointer as an error produces the same bug.

What the interviewer is looking for: you can explain value semantics versus reference-like behaviour from the header layouts, and why storing a non-pointer value in an interface usually needs a heap allocation (the data word must be a pointer).

More on Memory, GC & Runtime Internals

All 38 Memory, GC & Runtime Internals questions