skip to content

What two parts does a Go interface value hold, and when is it equal to nil?

level: juniorimportance: must knowfreq 66%

answer

  1. it is not just a pointer
  2. two halves travel together
  3. assignment fills one half even when empty
  4. nil means neither half is set
  5. %T tells you which half is occupied

basics

~20 s

A Go interface value holds two halves: a dynamic type and a value of that type. It is nil only when both halves are empty. An interface that has been given a type is non-nil even if the value stored is a nil pointer.

solid answer

~40 s

A variable of interface type in Go is a pair: the dynamic type of whatever was assigned to it, and the value of that type. A freshly declared `var w io.Writer` has both halves empty, so `w == nil` is true and calling a method on it panics. The moment you assign anything concrete, the type half is filled in, and the interface is no longer nil. That is why assigning a nil `*os.File` to an `io.Writer` gives a non-nil interface: the value half is nil, but the type half says `*os.File`. `x == nil` on an interface therefore asks are both halves empty, not is the stored pointer nil. `fmt.Printf("%T", x)` prints the dynamic type currently held, or `<nil>` when the interface is genuinely empty.

code

go · 7 lines
go
var w io.Writer
fmt.Println(w == nil) // true: no type, no value

var f *os.File
w = f
fmt.Println(w == nil) // false: the type half now says *os.File
fmt.Printf("%T\n", w) // *os.File

go deeper

for a junior

Be ready to state the two halves out loud and to say that an interface is nil only when both are empty. Show it with a two-line snippet assigning a nil pointer to an interface variable.

for a middle

Explain why assignment fills the type half regardless of the value, and what that means for method dispatch on a nil receiver. Know that %T reveals the dynamic type.

for a senior

Show that you use this when debugging: reach for %T rather than guessing, and recognise the shape of code where a nil concrete pointer reaches an interface variable and silently defeats a nil check.

for a principal

Own the convention. Decide whether your codebase allows concrete pointer types in interface-returning positions at all, and make that a written review rule rather than tribal knowledge.

### An interface value is a pair In Go, a variable whose type is an interface does not store a concrete value directly. It stores two things: 1. the **dynamic type** - which concrete type was last assigned to it (`*os.File`, `bytes.Buffer`, `int`, ...), and 2. the **value** of that type - the pointer, struct or number itself. Both halves come along together. The dynamic type is what lets the runtime find the right method to call when you write `w.Write(p)` without knowing which concrete type is inside. ### The zero value The zero value of every interface type is an interface with **both halves empty**. That is what people mean by a nil interface: ```go var w io.Writer // dynamic type: none, value: none fmt.Println(w == nil) // true ``` Calling a method on it has nowhere to dispatch, so it panics at runtime with `invalid memory address or nil pointer dereference`. This is the ordinary meaning of a nil check on an interface: has anything been put in here yet. ### Assignment fills the type half Assigning a concrete value writes both halves: ```go var buf bytes.Buffer var w io.Writer = &buf // type half: *bytes.Buffer, value half: address of buf ``` The crucial consequence is that the type half is filled in **whether or not the value half is meaningful**. A nil pointer is a perfectly good value of a pointer type: ```go var f *os.File // nil pointer var w io.Writer = f // type half: *os.File, value half: nil fmt.Println(w == nil) // false - one half is occupied ``` So `w == nil` is false. Nothing is broken here; the interface faithfully records that it is holding a `*os.File` that happens to be nil. This is the root of the typed-nil surprise people hit when a function returns a nil concrete pointer through an interface return type. ### Reading nil as a question about both halves The rule to memorise is one sentence: **an interface value is nil only when it holds no type and no value.** Every confusing case follows from it. Two useful corollaries: - Comparing an interface to `nil` never panics, no matter what is inside it. - `nil` on the right-hand side of the comparison is the untyped nil, which for an interface operand means the empty pair - not a nil pointer of some type. ### Seeing what is inside The cheapest diagnostic is the `%T` verb, which prints the dynamic type: ```go fmt.Printf("%T\n", w) // *os.File, or <nil> when the interface is empty ``` When a nil check is behaving unexpectedly, `%T` immediately separates the two situations: `<nil>` means the interface is truly empty, and any concrete type name means the type half is occupied and the interface cannot be nil. ### Methods still dispatch on a typed nil A subtle and useful point: if the interface holds a nil pointer, the method call still works - the runtime knows the type, so it knows which function to run. It is passed a nil receiver. Whether that panics depends entirely on the method body: ```go type Counter struct{ n int } func (c *Counter) Len() int { if c == nil { return 0 // nil-tolerant: no dereference } return c.n } ``` A method written like that behaves fine through an interface holding a nil `*Counter`. A method that dereferences the receiver panics. That is why the panic, when it comes, is a nil pointer dereference **inside the method**, not at the call site. ### Why Go was designed this way The alternative - collapsing an interface holding a nil pointer down to a nil interface - would throw away the type information, and with it the ability to call nil-tolerant methods, to distinguish which implementation was chosen, and to keep interface comparison well defined. Go pays for that with one famous trap and gains a model you can state in a single line: two halves, nil only when both are empty. ### What to say in an interview Name the pair, name the rule, then give the one-line demonstration with a nil pointer assigned to an interface variable and the resulting `false` from the nil comparison. Reaching for `%T` as the diagnostic shows you have debugged it rather than only read about it.

  • What happens if you call a method on an interface variable that was never assigned anything?
    It panics at runtime with `invalid memory address or nil pointer dereference`. With both halves empty there is no dynamic type, so the runtime has no method to dispatch to. This is different from an interface holding a nil pointer, where the method does run with a nil receiver and only panics if the body dereferences it.
  • How do you see which concrete type an interface value is currently holding?
    Print it with the `%T` verb: `fmt.Printf("%T", x)`. It prints the dynamic type name, such as `*os.File`, or `<nil>` when the interface is genuinely empty. That single line separates an empty interface from one holding a nil pointer, which is usually the whole question when a nil check misbehaves.
  • Can a method be called successfully on an interface holding a nil pointer?
    Yes. The dynamic type is known, so the method runs with a nil receiver. If the body guards with `if c == nil` or never touches the receiver's fields, it returns normally. It panics only when the body dereferences the nil receiver, and the panic then points inside the method, not at the call site.

Think of a parcel with a shipping label. The label names the contents type and the box holds the goods. An empty box with a label on it is still a labelled parcel - it is not the same as no parcel at all.

saying these in an interview costs you the question

  • Says an interface value is just a pointer to the concrete value
  • Assumes assigning a nil pointer leaves the interface nil
  • Thinks a nil check on an interface inspects the stored pointer
  • Claims comparing an interface to nil can panic
  • Believes a method call on a typed nil always panics at the call site