Why does reflect.TypeOf return nil for a nil interface value, and how do you stop an inspector panicking on it?
answer
- an empty interface value carries no type
- reflect.Type is itself an interface
- ValueOf is gentler than TypeOf here
- one method is safe on the zero Value
- a typed nil still has a type
basics
~20 sA 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 linesvar 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 dereferencego deeper
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.
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.
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.
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