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 typereflect.TypeOf(value)to get areflect.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]stringis an array of exactly 3 strings[]stringis 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.
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:
[]string{"go", "is", "fun"}creates a slice of strings.var value any = ...stores that slice inside an interface value.- The interface still remembers the concrete type:
[]string. fmt.Printf("%T\n", value)asks Go to print the concrete type stored in the interface.- 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:
reflect.TypeOf(value)reads the runtime type inside the interface.- It returns a
reflect.Typerepresenting[]string. 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:
value.([]string)checks whether the concrete type insidevalueis[]string.- If yes,
okbecomestrueandlinesgets the real slice. - If not,
okbecomesfalseand the zero value is assigned. - 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.
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
%Tto print - use type assertions to extract expected types
- use type switches when multiple types are allowed
- use
reflectwhen 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.Printfexpects the first argument to be a format stringreflect.TypeOf(lines)returns areflect.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]stringand[]stringare 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
| Approach | What it does | Best use case | Example |
|---|---|---|---|
fmt.Printf("%T", v) | Prints the concrete type | Quick debugging and logging | fmt.Printf("%T\n", v) |
reflect.TypeOf(v) | Returns a reflect.Type value | When code needs type metadata | t := reflect.TypeOf(v) |
| Type assertion | Converts from interface to expected type | When you expect one specific type | s, ok := v.([]string) |
| Type switch | Branches across multiple types | When several types are supported | switch 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
| Feature | Type assertion | Reflection |
|---|---|---|
| Simple to read | Yes | Usually less so |
Safe with ok form | Yes | Not a conversion tool by itself |
| Good for expected known types | Yes | Sometimes unnecessary |
| Useful for generic runtime inspection | Limited | Yes |
Array vs slice
| Type | Example | Fixed size? | Common in Go? |
|---|---|---|---|
| Array | [3]string | Yes | Less common |
| Slice | []string | No | Very common |
In many real programs, if you think you have an array of strings, you probably actually have a slice of strings.
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
%Twhen you just want to print a type. - Use
reflect.TypeOfwhen 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.Printfneeds a format string first.- Arrays and slices are different types.
[]stringis a slice,[3]stringis an array.- Values from
container/listare stored asany/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 printingreflect.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.
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
- Create a
container/listand add at least three different kinds of values. - Iterate through the list and print the type of each value.
- Use a type assertion to detect whether a value is a
[]string. - If the value is a
[]string, print its contents. - If the value is not a
[]string, print a message showing the actual type.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.