skip to content

Why does reflect.TypeOf return nil for a nil interface value, and how do you stop an inspector panicking on it?

level: seniorimportance: should knowfreq 33%

answer

  1. an empty interface value carries no type
  2. reflect.Type is itself an interface
  3. ValueOf is gentler than TypeOf here
  4. one method is safe on the zero Value
  5. a typed nil still has a type

basics

~20 s

A nil interface value carries no dynamic type, so there is nothing for reflect.TypeOf to describe and it returns a nil reflect.Type; any method call on that nil panics. reflect.ValueOf returns the zero Value instead, so guard with Value.IsValid before touching it.

solid answer

~40 s

`reflect.TypeOf` reads the dynamic type out of the interface it is handed. A nil interface value has no dynamic type at all, so there is genuinely nothing to describe and the function returns a nil `reflect.Type` — and because `reflect.Type` is an interface, calling `Name()` or `Kind()` on it dereferences nil and panics. `reflect.ValueOf(nil)` is friendlier: it returns the *zero* `reflect.Value`, whose `IsValid()` is `false` and whose `Kind()` is `reflect.Invalid`, but whose `Type()`, `Interface()` and `IsNil()` all panic. So the first line of any inspector is `if !rv.IsValid()`. The contrast that trips people is an interface holding a *typed* nil, say `any((*bytes.Buffer)(nil))`: that does have a dynamic type, so `TypeOf` returns `*bytes.Buffer`, `IsValid()` is `true`, `Kind()` is `Pointer` and only `IsNil()` is `true`. Two different nils, two different checks.

code

go · 8 lines
go
var v any // nil interface value: no dynamic type

fmt.Println(reflect.TypeOf(v) == nil)     // true
fmt.Println(reflect.ValueOf(v).IsValid()) // false
fmt.Println(reflect.ValueOf(v).Kind())    // invalid

// reflect.TypeOf(v).Name() panics:
// invalid memory address or nil pointer dereference

go deeper

for a junior

Know that passing an untyped nil to reflect.TypeOf gives you nil back, and that calling a method on that result crashes the program. Check the value before you inspect it.

for a middle

Explain that an interface value carries a dynamic type and data, that an empty one has neither, and that reflect.ValueOf answers with a zero Value whose Kind is Invalid.

for a senior

Demonstrate the ordered guards a production walker needs, distinguish an untyped nil from an interface holding a typed nil, and pin the behaviour with a table-driven test over mixed inputs.

for a principal

Decide how much of a shared helper's contract should be defensive at all: whether it returns a label, an error, or refuses nil inputs at the boundary, and who is on the hook when it panics inside someone else's request path.

## The scenario Someone onboarding to the codebase adds a terminal inspector that prints whatever value the session just evaluated — a helper taking `any` and reporting the type and contents. It works on everything they try, then panics on a user in the wild with `invalid memory address or nil pointer dereference` pointing at a line that looks harmless: `reflect.TypeOf(v).String()`. ## Why TypeOf returns nil `reflect.TypeOf(i any) reflect.Type` does not inspect a variable; it inspects the interface value it was handed. An interface value is conceptually a pair — a dynamic type and the data of that type — and a *nil interface value* is the one where both halves are absent. There is no type in it. `TypeOf` therefore has nothing to return and returns nil. The sharp edge is that `reflect.Type` is itself an interface type, so "nil" here means a nil interface, not a typed nil object. Calling any method on it is a method call on a nil interface value, which panics immediately. There is no lenient descriptor for "no type" to fall back on. ## Why ValueOf is different `reflect.ValueOf(nil)` does not panic and does not return anything you can compare to nil — `reflect.Value` is a struct, so `rv != nil` does not even compile. It returns the **zero Value**, and the zero Value has a deliberately small safe surface: - `IsValid()` returns `false`. This is the canonical check. - `Kind()` returns `reflect.Invalid`, and is safe to call. - `String()` returns `"<invalid Value>"`, also safe. - Everything else — `Type()`, `Interface()`, `Int()`, `Len()`, `IsNil()`, `IsZero()` — panics. So the shape of a robust inspector is: take the `Value` first, check `IsValid()`, and only then ask anything about it. ```go func describe(v any) string { rv := reflect.ValueOf(v) if !rv.IsValid() { return "untyped nil" } if rv.Kind() == reflect.Pointer && rv.IsNil() { return "nil " + rv.Type().String() } return rv.Type().String() } ``` ## The contrast that actually causes the bug The untyped nil is only half the story, and the other half is what makes people write the wrong guard. Consider: ```go var buf *bytes.Buffer // a nil pointer var v any = buf // an interface holding a typed nil ``` Here the interface is *not* empty: its dynamic type is `*bytes.Buffer` and its data is a nil pointer. So `reflect.TypeOf(v)` returns a perfectly good `*bytes.Buffer` descriptor, `reflect.ValueOf(v).IsValid()` is `true`, `Kind()` is `reflect.Pointer`, and the only thing that reports nil-ness is `IsNil()`. The same is true of a nil map, a nil slice, a nil channel and a nil func value: each has a dynamic type, so each produces a valid `reflect.Value` whose `IsNil()` is `true`. That gives you two genuinely distinct questions, and a correct inspector asks them in order: 1. *Is there a type at all?* → `IsValid()`. 2. *Is the typed thing nil?* → `IsNil()`, and only for the kinds that can be nil — `Chan`, `Func`, `Interface`, `Map`, `Pointer`, `Slice`. `IsNil()` panics on any other kind, so it is guarded by a `Kind` check, not called blindly. Getting the order wrong is exactly how the second version of this bug appears: the author adds `rv.IsNil()` as the fix for the first panic and now panics on `describe(nil)` with "call of reflect.Value.IsNil on zero Value". ## Pinning it with a test This is cheap to lock down and almost impossible to reason about from the call site, so the fix ships with a table-driven test over a slice of `any` inputs — `nil`, `(*bytes.Buffer)(nil)`, `[]int(nil)`, `map[string]int(nil)`, `0`, `""`, a struct value, a channel — asserting the label for each. The test is the artefact that stops the next person re-introducing the same guard in the wrong order, and it runs in microseconds. ## What good sounds like Say that a nil interface has no dynamic type, so `TypeOf` has nothing to describe and returns nil, and that `reflect.Type` being an interface is why calling a method on that nil panics. Say that `ValueOf` returns the zero Value instead, with `IsValid()` false and `Kind()` `Invalid` as the only safe things to touch. Then draw the contrast with a typed nil, where the type is present and only `IsNil()` is true — and note that `IsNil()` itself panics both on the zero Value and on kinds that cannot be nil, which is why the checks are ordered.

  • Does reflect.ValueOf(nil) panic?
    No. It returns the zero `reflect.Value`. `IsValid()` is `false`, `Kind()` is `reflect.Invalid` and `String()` gives `"<invalid Value>"` — those are safe. `Type()`, `Interface()`, `IsNil()` and the typed accessors all panic on it, so `IsValid()` is the first thing any walker calls.
  • Why is a nil map's reflect.Value valid when an untyped nil's is not?
    Because a nil map still has a dynamic type. Assigning `map[string]int(nil)` to an `any` produces an interface whose type half is `map[string]int` and whose data half is a nil map, so there is something to describe: `IsValid()` is `true`, `Kind()` is `Map`, and `IsNil()` is `true`.
  • How would you write the regression test for this?
    Table-driven over a slice of `any`: `nil`, a nil pointer of some concrete type, `[]int(nil)`, `map[string]int(nil)`, plus a couple of ordinary values. Assert the label the inspector produces for each. It runs in microseconds and pins the guard order, which is the part a future reader will otherwise get wrong.

Asking for the label on an empty box: there is no box, so there is no label, and reading a label that does not exist is what panics. A box that is present but empty inside still has a perfectly readable label.

saying these in an interview costs you the question

  • Claims reflect.TypeOf never returns nil
  • Guards with rv != nil, which does not compile
  • Calls IsNil on any Value without a Kind check
  • Says an interface holding a nil pointer has no dynamic type
  • Calls Type() before checking IsValid