Question

How to Find the Type of an Object in Go

gogo-reflect

Question

How can I find the type of a value in Go?

In Python, I would normally use a built-in operator or function to inspect an object's type. I want to do something similar in Go.

For example, I am iterating through a container like this:

for e := dlist.Front(); e != nil; e = e.Next() {
    lines := e.Value
    fmt.Printf(reflect.TypeOf(lines))
}

In this loop, I want to determine the type of lines. In my case, the value is expected to be a slice or array of strings, but I am not able to print or inspect its type correctly.

Short Answer

By the end of this page, you will understand how Go represents types at runtime, how to inspect a value's type using reflect.TypeOf, when fmt.Printf("%T", value) is simpler, and how to work with interface values such as e.Value from a container. You will also learn the difference between arrays and slices, plus common mistakes beginners make when checking types in Go.

Concept

In Go, the type of a variable is usually known at compile time. Most of the time, you do not need to ask for a value's type because Go's static type system already enforces it.

However, sometimes you work with values stored in an interface{} value, such as:

  • container elements
  • decoded JSON data
  • generic helper functions
  • logging and debugging tools

In those situations, the concrete type is hidden behind the interface, and you may want to inspect it at runtime.

Go provides two common ways to do this:

  • fmt.Printf("%T", value) to print the type
  • reflect.TypeOf(value) to get a reflect.Type

For example:

fmt.Printf("%T\n", value)

or:

t := reflect.TypeOf(value)
fmt.Println(t)

This matters because many Go APIs store values as any or interface{}. When you pull a value back out, you often need to:

  • inspect its type
  • convert it with a type assertion
  • branch with a type switch

A very important detail here is that Go distinguishes between arrays and slices:

  • [3]string is an array of exactly 3 strings
  • []string is a slice of strings

Beginners often say "array of strings" when they actually have a slice. In many Go containers and APIs, the stored value is more likely to be a slice like []string.

In your example, e.Value is likely an interface{} value. reflect.TypeOf(lines) will work, but fmt.Printf(reflect.TypeOf(lines)) is not the correct way to print it because fmt.Printf expects a format string as its first argument.

Mental Model

Think of an interface{} value in Go like a sealed box with two labels inside:

  • the real value
  • the real type

When you store something in interface{}, you can no longer see the exact type directly from the variable name alone. You need a tool to peek at the label.

  • fmt.Printf("%T", value) is like asking, "What type label is on this box?"
  • reflect.TypeOf(value) is like opening a small inspection window to read the type label programmatically
  • a type assertion like value.([]string) is like saying, "I believe this box contains a []string; give it to me as that type"

So when you iterate over e.Value, you are handling boxes. The actual type is still there, but you must inspect or assert it.

Take Quiz

Syntax and Examples

Print a value's type

The easiest way to print a type in Go is:

fmt.Printf("%T\n", value)

Example:

package main

import "fmt"

func main() {
    x := []string{"a", "b", "c"}
    fmt.Printf("%T\n", x)
}

Output:

[]string

Use reflect.TypeOf

If you want the type as a value, use reflect.TypeOf:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    x := []string{"a", "b", "c"}
    t := reflect.TypeOf(x)
    fmt.Println(t)
}

Output:

[]string

Correct way in your loop

Your code should look like this:

for e := dlist.Front(); e != nil; e = e.Next() {
    lines := e.Value
    fmt.Printf("%T\n", lines)
}

Or with reflection:

for e := dlist.Front(); e != nil; e = e.Next() {
    lines := e.Value
    fmt.Println(reflect.TypeOf(lines))
}

If you expect a []string

If the value should be a slice of strings, use a type assertion:

for e := dlist.Front(); e != nil; e = e.Next() {
    lines, ok := e.Value.([]string)
    if !ok {
        fmt.Printf("unexpected type: %T\n", e.Value)
        continue
    }

    fmt.Println("slice length:", len(lines))
}

This is often better than only printing the type because it lets you safely use the value.

Array vs slice example

package main

import "fmt"

func main() {
    a := [3]string{"x", "y", "z"}
    s := []string{"x", "y", "z"}

    fmt.Printf("a: %T\n", a)
    fmt.Printf("s: %T\n", s)
}

Output:

a: [3]string
s: []string

These are different types in Go.

Step by Step Execution

Consider this example:

package main

import "fmt"

func main() {
    var value any = []string{"go", "is", "fun"}

    fmt.Printf("%T\n", value)
}

Step by step:

  1. []string{"go", "is", "fun"} creates a slice of strings.
  2. var value any = ... stores that slice inside an interface value.
  3. The interface still remembers the concrete type: []string.
  4. fmt.Printf("%T\n", value) asks Go to print the concrete type stored in the interface.
  5. The result is:
[]string

Now with reflection:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var value any = []string{"go", "is", "fun"}

    t := reflect.TypeOf(value)
    fmt.Println(t)
}

Step by step:

  1. reflect.TypeOf(value) reads the runtime type inside the interface.
  2. It returns a reflect.Type representing []string.
  3. fmt.Println(t) prints that type.

Now with a type assertion:

package main

import "fmt"

func main() {
    var value any = []string{"go", "is", "fun"}

    lines, ok := value.([]string)
    if !ok {
        fmt.Println("not a []string")
        return
    }

    fmt.Println(lines[0])
}

Step by step:

  1. value.([]string) checks whether the concrete type inside value is []string.
  2. If yes, ok becomes true and lines gets the real slice.
  3. If not, ok becomes false and the zero value is assigned.
  4. This is the safe way to move from inspection to actual use.

Real World Use Cases

Runtime type inspection is useful in several practical situations:

Working with containers

Packages like container/list store element values as any or interface{}. When reading them back, you may need to inspect or assert the real type.

value := e.Value
fmt.Printf("%T\n", value)

Debugging unknown data

When debugging, printing %T helps you confirm what kind of value you are actually receiving.

fmt.Printf("received type: %T\n", payload)

Handling decoded JSON

When JSON is decoded into map[string]any, fields may contain different runtime types.

var data map[string]any
// after decoding JSON
fmt.Printf("type of age: %T\n", data["age"])

Generic utilities and libraries

Reusable helpers sometimes accept any and branch based on type.

switch v := input.(type) {
case string:
    fmt.Println("string:", v)
case []string:
    fmt.Println("slice of strings:", v)
default:
    fmt.Printf("unknown type: %T\n", v)
}

Validation and parsing

Programs that accept flexible input often inspect types before processing them further.

Take Quiz

Real Codebase Usage

In real Go codebases, developers usually do more than just print a type.

1. Prefer type assertions when you expect a specific type

If a value should be []string, assert that directly:

lines, ok := e.Value.([]string)
if !ok {
    return fmt.Errorf("expected []string, got %T", e.Value)
}

This is common in validation and parsing code.

2. Use %T for logging and debugging

For quick diagnostics, %T is often simpler than reflect.TypeOf:

log.Printf("unexpected type: %T", value)

3. Use type switches for multiple possible types

When several input types are valid, a type switch is clearer:

switch v := value.(type) {
case string:
    fmt.Println("single string", v)
case []string:
    fmt.Println("many strings", v)
default:
    fmt.Printf("unsupported type: %T\n", v)
}

4. Guard clauses for invalid types

A common pattern is to check type early and return immediately:

lines, ok := e.Value.([]string)
if !ok {
    return
}

This keeps the rest of the code simple.

5. Reflection is usually a last resort

Reflection is useful, but many Go developers avoid it unless necessary because:

  • it is less simple than normal type-safe code
  • it can be harder to read
  • type assertions and type switches are often enough

A good rule is:

  • use %T to print
  • use type assertions to extract expected types
  • use type switches when multiple types are allowed
  • use reflect when you truly need runtime type metadata

Common Mistakes

Mistake 1: Passing reflect.TypeOf(...) directly to fmt.Printf

Broken code:

fmt.Printf(reflect.TypeOf(lines))

Why it fails:

  • fmt.Printf expects the first argument to be a format string
  • reflect.TypeOf(lines) returns a reflect.Type, not a format string

Correct code:

fmt.Println(reflect.TypeOf(lines))

or:

fmt.Printf("%T\n", lines)

Mistake 2: Confusing arrays and slices

Broken assumption:

// expecting array, but actual value is a slice
lines, ok := e.Value.([3]string)

If the stored value is []string, this will fail.

Correct idea:

lines, ok := e.Value.([]string)

Remember:

  • [3]string and []string are different types

Mistake 3: Using a type assertion without checking success

Broken code:

lines := e.Value.([]string)

This panics if the value is not []string.

Safer version:

lines, ok := e.Value.([]string)
if !ok {
    fmt.Printf("got %T instead of []string\n", e.Value)
    return
}

Mistake 4: Expecting Go to work like Python's dynamic typing

In Go, types are usually known statically. Runtime inspection is mostly needed when working with interface values.

Mistake 5: Forgetting nil behavior

var x any = nil
fmt.Printf("%T\n", x)

This prints <nil> because there is no concrete type stored.

Also note that a typed nil inside an interface behaves differently:

var s []string = nil
var x any = s
fmt.Printf("%T\n", x)

This prints:

[]string

Because the interface still knows the concrete type.

Comparisons

ApproachWhat it doesBest use caseExample
fmt.Printf("%T", v)Prints the concrete typeQuick debugging and loggingfmt.Printf("%T\n", v)
reflect.TypeOf(v)Returns a reflect.Type valueWhen code needs type metadatat := reflect.TypeOf(v)
Type assertionConverts from interface to expected typeWhen you expect one specific types, ok := v.([]string)
Type switchBranches across multiple typesWhen several types are supportedswitch v := x.(type)

%T vs reflect.TypeOf

fmt.Printf("%T\n", value)
fmt.Println(reflect.TypeOf(value))

Both can show the type, but %T is usually simpler if you only want to print it.

Type assertion vs reflection

FeatureType assertionReflection
Simple to readYesUsually less so
Safe with ok formYesNot a conversion tool by itself
Good for expected known typesYesSometimes unnecessary
Useful for generic runtime inspectionLimitedYes

Array vs slice

TypeExampleFixed size?Common in Go?
Array[3]stringYesLess common
Slice[]stringNoVery common

In many real programs, if you think you have an array of strings, you probably actually have a slice of strings.

Take Quiz

Cheat Sheet

// Print the type of a value
fmt.Printf("%T\n", value)

// Get the type as a reflect.Type
import "reflect"
t := reflect.TypeOf(value)
fmt.Println(t)

// Safe type assertion
lines, ok := value.([]string)
if !ok {
    fmt.Printf("unexpected type: %T\n", value)
}

// Type switch
switch v := value.(type) {
case string:
    fmt.Println("string", v)
case []string:
    fmt.Println("slice of strings", v)
default:
    fmt.Printf("unknown type: %T\n", v)
}

Key rules:

  • Use %T when you just want to print a type.
  • Use reflect.TypeOf when you need the type programmatically.
  • Use type assertions when you expect a specific concrete type.
  • Use a type switch when multiple types are valid.
  • fmt.Printf needs a format string first.
  • Arrays and slices are different types.
  • []string is a slice, [3]string is an array.
  • Values from container/list are stored as any/interface{} and often need assertion.

FAQ

How do I print the type of a variable in Go?

Use:

fmt.Printf("%T\n", value)

This prints the concrete runtime type.

What is the Go equivalent of Python type inspection?

The closest common options are:

  • fmt.Printf("%T", value) for printing
  • reflect.TypeOf(value) for getting a type value

Why does fmt.Printf(reflect.TypeOf(x)) not work?

Because fmt.Printf expects a format string as the first argument. Use:

fmt.Println(reflect.TypeOf(x))

or:

fmt.Printf("%T\n", x)

How do I know if a value is a []string in Go?

Use a type assertion:

lines, ok := value.([]string)

If ok is true, the value is a []string.

What is the difference between []string and [3]string?

[]string is a slice with dynamic length. [3]string is an array with exactly 3 elements. They are different types.

Should I use reflection to check types in Go?

Only when needed. If you only need to print the type, %T is simpler. If you expect a specific type, a type assertion is usually better.

Why do I need type inspection when using container/list?

Because container/list stores values as any/interface{}. When you read a value back, you need to inspect or assert its real concrete type.

Take Quiz

Mini Project

Description

Build a small Go program that stores different kinds of values in a container/list and then inspects each item's type. This project demonstrates how runtime type inspection works when values are stored as any, and how to safely handle expected types such as []string.

Goal

Create a program that iterates through a list, prints each element's type, and safely processes values that are slices of strings.

Requirements

  1. Create a container/list and add at least three different kinds of values.
  2. Iterate through the list and print the type of each value.
  3. Use a type assertion to detect whether a value is a []string.
  4. If the value is a []string, print its contents.
  5. If the value is not a []string, print a message showing the actual type.
Take Quiz

Keep learning

Related questions

View more