skip to content

In Go's reflect package, how does Value.Kind differ from Type.Name, and which should you switch on?

level: middleimportance: should knowfreq 40%

answer

  1. one is a category, one is a label
  2. the category list never grows
  3. type UserID int is still an Int
  4. unnamed composite types have no name
  5. walkers and encoders switch on the category

basics

~20 s

Kind is one of a fixed set of built-in categories describing a value's underlying representation, such as Int, Slice or Struct. Name is the declared name of a named type and is empty for unnamed types. Structural code switches on Kind.

solid answer

~40 s

`Kind` answers "what category of thing is this, at the representation level" and comes from a closed set of constants — `Bool`, the sized integer and float kinds, `String`, `Slice`, `Array`, `Map`, `Struct`, `Pointer`, `Chan`, `Func`, `Interface`, `UnsafePointer`, plus `Invalid`. `Type.Name()` answers "what is this type called", and returns `""` for unnamed composite types like `[]int` or `map[string]int`; `Type.String()` gives the readable package-qualified form. The distinction bites with defined types: for `type UserID int`, `Kind()` is `reflect.Int` while `Name()` is `"UserID"`. Anything structural — a walker, an encoder, a comparer — switches on `Kind`, because that is what decides how the bytes can be read. When you need one *specific* type instead of a category, compare `reflect.Type` identity rather than names, since names are not unique across packages.

code

go · 10 lines
go
type UserID int

t := reflect.TypeOf(UserID(7))
fmt.Println(t.Kind()) // int
fmt.Println(t.Name()) // UserID

u := reflect.TypeOf([]UserID{})
fmt.Println(u.Kind()) // slice
fmt.Println(u.Name()) // (empty: []UserID is unnamed)
fmt.Println(u.String()) // []-prefixed readable form

go deeper

for a junior

Remember that Kind is a fixed category like Int, Slice or Struct, while Name is the declared name of the type and is often empty. Be able to give one example of each.

for a middle

Explain why type UserID int reports Kind Int and Name UserID, why unnamed composite types have no name, and why structural code switches on Kind rather than on names.

for a senior

Show the failure you have actually hit: a walker keyed on names silently skips unnamed types and collides across packages, so identity checks compare reflect.Type values instead.

for a principal

Own the call about how much of a system's behaviour is allowed to depend on run-time type inspection at all, and what that costs a team in debuggability compared with explicit registration.

## Two different questions `reflect` exposes two very different notions of "type", and mixing them up is the most common early mistake with the package. **Kind** is the *representation category*. It is a `reflect.Kind`, a small integer with a fixed set of constants: `Invalid`, `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64`, `Uintptr`, `Float32`, `Float64`, `Complex64`, `Complex128`, `Array`, `Chan`, `Func`, `Interface`, `Map`, `Pointer`, `Slice`, `String`, `Struct`, `UnsafePointer`. That list never grows when your program declares new types. Kind tells you how the value is laid out and therefore which operations are legal on it: you may call `Len()` on a `Slice`, `Array`, `Map`, `String` or `Chan`, `Int()` on the signed integer kinds, `NumField()` on a `Struct`. **Name** is the *declared identifier*. `Type.Name()` returns `"UserID"` for `type UserID int`, `"Time"` for `time.Time`, `"int"` for the predeclared `int`, and the empty string for any unnamed composite type — `[]int`, `map[string]int`, `*User`, `struct{ A int }`, `func(int) error`. `Type.String()` is the friendlier sibling: it always produces something readable, package-qualified for defined types (`store.UserID`, `time.Time`) and structural for unnamed ones (`[]int`). ## Where the difference shows ```go type UserID int t := reflect.TypeOf(UserID(7)) t.Kind() // reflect.Int — the underlying representation t.Name() // "UserID" — the declared name ``` A defined type built on `int` *is* an `Int` as far as the representation goes: it takes the same space, and `Value.Int()` reads it happily. What it is not is `int` — assignment between them requires a conversion, and `reflect.TypeOf(UserID(7)) == reflect.TypeOf(7)` is false. So `Kind` deliberately erases the distinction that the type system keeps. ## Which one to switch on Almost always `Kind`. A struct walker, a deep-copier, a comparer or an encoder needs to know how to *read* the value, and that is exactly what `Kind` reports. Code shaped like ```go switch v.Kind() { case reflect.Struct: // iterate fields case reflect.Slice, reflect.Array: // iterate elements case reflect.Pointer, reflect.Interface: // step through to what is inside default: // a leaf: read it } ``` handles every value in the language with a couple of dozen cases. The same code written against `Name()` would have to enumerate names it cannot know, and would silently miss every unnamed type, whose `Name()` is `""`. Switching on `Name()` is a genuine bug source for a second reason: names are not unique. Two packages can both declare `type Config struct{…}`, and both report `Name() == "Config"`. If you need to recognise one particular type, compare `reflect.Type` values directly — they are canonical, so `t == reflect.TypeOf(time.Time{})` is an exact identity test — or use `reflect.TypeFor[time.Time]()`, which says the same thing without constructing a value. ## Two details worth carrying First, a `reflect.Value` obtained from `reflect.ValueOf` never reports `Kind` `Interface`. The argument is boxed into the `any` parameter and reflection immediately unwraps it, so you always see the concrete dynamic type. `Interface` shows up only when you reach a value *through* something — a struct field, a map or slice element, whose declared type is an interface type. Second, `Name()` on a predeclared type is not empty: `reflect.TypeOf(3).Name()` is `"int"`, and its `PkgPath()` is `""` because predeclared types belong to no package. Non-empty name plus empty package path is how you spot a builtin; non-empty both is a defined type from a package; empty name is an unnamed composite. ## The interview shape of this The expected answer is: Kind is a closed set of representation categories, Name is a declared identifier that is often empty, `type UserID int` has Kind `Int` and Name `"UserID"`, structural code switches on Kind, and specific-type checks compare `reflect.Type` identity rather than names. Being able to say why the walker breaks if you switch on names — unnamed types report `""`, and names collide across packages — is what makes the answer sound lived-in rather than recited.

  • What does reflect.TypeOf([]byte{}).Name() return?
    The empty string, because `[]byte` is an unnamed composite type — only defined and predeclared types have names. `String()` on it returns `"[]uint8"`, since `byte` is an alias for `uint8` and the printed form uses the underlying name. `Kind()` is `reflect.Slice`, and `Elem().Kind()` is `reflect.Uint8`.
  • How do you test for one specific type such as time.Time rather than a category?
    Compare `reflect.Type` values with `==`. The runtime hands out one canonical descriptor per type, so `t == reflect.TypeFor[time.Time]()` is an exact identity test that no name comparison can match — two packages can each declare a `Config`, and both report `Name() == "Config"`.
  • Can a reflect.Value ever report Kind Interface?
    Not one obtained straight from `reflect.ValueOf`: the argument is boxed into the `any` parameter and immediately unwrapped, so you see the concrete dynamic type. You reach `Kind` `Interface` only through a value whose declared type is an interface — a struct field, a map value, a slice element.

saying these in an interview costs you the question

  • Says Kind returns the type's declared name
  • Expects Name to return "[]int" for a slice
  • Switches on Name to handle all integers
  • Assumes every defined type gets its own Kind
  • Compares type names across packages to test identity