Last modified: Jan 22, 2026 By Alexander Williams
Check Variable Type at Runtime in Go
Go is a statically typed language. Types are checked at compile time.
But sometimes you need to check a variable's type while the program is running.
This is called runtime type checking. It is useful for handling unknown data.
You might get data from an external API or a user input. Knowing the type helps you process it correctly.
This guide explains two main methods. You will learn about the reflect package and type assertions.
Why Check Types at Runtime?
Most Go code does not need runtime type checks. The compiler handles type safety.
However, some scenarios require flexibility. You may be working with empty interfaces.
The interface{} type can hold any value. It is also written as any in newer Go versions.
When you have a variable of type interface{}, you lose type information. Runtime checks restore it.
Common use cases include JSON unmarshaling, plugin systems, and generic data structures. Understanding Go Default Values for Uninitialized Variables is also key when dealing with empty interfaces.
Method 1: Using the Reflect Package
The reflect package provides powerful tools for introspection. It lets you examine types and values at runtime.
Import the package with import "reflect". The key function is reflect.TypeOf().
This function takes any value and returns its type as a reflect.Type.
You can then compare this type or get its name. Let's look at a basic example.
package main
import (
"fmt"
"reflect"
)
func main() {
var myVar interface{} = "Hello, Go!"
// Get the type of the variable
varType := reflect.TypeOf(myVar)
// Print the type name
fmt.Println("Variable type is:", varType)
}
Variable type is: string
The output shows the type is string. The reflect.TypeOf() function is simple to use.
You can also get the underlying kind of a type. Use the Kind() method on the reflect.Type.
This is useful for checking if a type is a slice, map, struct, or basic type.
func checkKind(val interface{}) {
t := reflect.TypeOf(val)
fmt.Printf("Type: %v, Kind: %v\n", t, t.Kind())
}
func main() {
checkKind(42) // int
checkKind(3.14) // float64
checkKind([]int{1,2,3}) // slice
checkKind(map[string]int{}) // map
}
Type: int, Kind: int
Type: float64, Kind: float64
Type: []int, Kind: slice
Type: map[string]int, Kind: map
Remember, reflection has a performance cost. Use it only when necessary.
Method 2: Using Type Assertions
Type assertions are a more common and idiomatic Go feature. They are used with interface values.
A type assertion checks if an interface value holds a specific type. The syntax is value.(Type).
If the assertion succeeds, you get the value as that type. If it fails, the program panics.
To avoid panic, use the two-value form. It returns a boolean indicating success.
package main
import "fmt"
func main() {
var i interface{} = "golang"
// Safe type assertion
s, ok := i.(string)
if ok {
fmt.Println("It's a string:", s)
} else {
fmt.Println("Not a string")
}
// This assertion will fail safely
n, ok := i.(int)
if ok {
fmt.Println("It's an int:", n)
} else {
fmt.Println("Not an int")
}
}
It's a string: golang
Not an int
This method is fast and efficient. It does not use reflection. It is the preferred way for known type possibilities.
You can combine it with a type switch for multiple checks. This is very powerful.
Knowing how to safely change types is crucial. Read our guide on Go Type Conversion: Safe Variable Changes for more details.
Using Type Switches for Multiple Checks
A type switch is a special form of switch statement. It compares the type of an interface value.
It simplifies checking against many possible types. The syntax uses the keyword type.
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Integer: %d\n", v)
case string:
fmt.Printf("String: %s\n", v)
case bool:
fmt.Printf("Boolean: %v\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
func main() {
describe(10)
describe("hello")
describe(true)
describe(12.5)
}
Integer: 10
String: hello
Boolean: true
Unknown type: float64
The variable v is of the matched type inside each case. This is very clean.
Type switches are perfect for functions that accept empty interfaces. They make your code clear and maintainable.
For more on handling different data structures, see Go List of Strings: Create, Iterate, Manipulate.
Practical Example: Handling JSON Data
A common real-world use is JSON decoding. The json.Unmarshal function often uses map[string]interface{}.
You need to check the types of values in the map to process them. Here is an example.
package main
import (
"encoding/json"
"fmt"
)
func processJSON(data []byte) {
var result map[string]interface{}
json.Unmarshal(data, &result)
for key, value := range result {
switch v := value.(type) {
case float64:
fmt.Printf("%s is a number: %v\n", key, v)
case string:
fmt.Printf("%s is a string: %v\n", key, v)
case []interface{}:
fmt.Printf("%s is an array with %d items\n", key, len(v))
case bool:
fmt.Printf("%s is a boolean: %v\n", key, v)
default:
fmt.Printf("%s has an unknown type: %T\n", key, v)
}
}
}
func main() {
jsonData := `{"name": "Alice", "age": 30, "tags": ["go", "backend"], "active": true}`
processJSON([]byte(jsonData))
}
name is a string: Alice
age is a number: 30
tags is an array with 2 items
active is a boolean: true
This pattern is very useful for building flexible APIs. It helps you handle diverse data structures.
Reflection vs. Type Assertion: Which to Choose?
You now know two methods. How do you choose?
Use type assertions when you know the possible types. It is faster and more idiomatic.
Use the reflect package when you need deep inspection. This includes getting struct field names or function signatures.
Reflection is more powerful but slower. Avoid it in performance-critical loops.
Type switches offer a great middle ground. They are clean and efficient for most dynamic type handling.
Proper Go Variable Declaration: var vs := Explained can also prevent type-related confusion from the start.
Conclusion
Checking variable types at runtime is a valuable skill in Go. It unlocks flexibility in your programs.
Use type assertions and type switches for most tasks. They are fast and clear.
Turn to the reflect package for advanced introspection needs. But use it sparingly.
Remember, Go's strength is static typing. Use runtime checks only when you truly need dynamic behavior.
Start by identifying the data sources in your program. Apply the right method to handle unknown types safely.