Type Assertions, Type Switches & any in Go 🔍
In Go, a variable of an interface type (such as any or error) can hold different concrete values at runtime. When you need to retrieve the underlying concrete value or check what type is stored inside, Go provides Type Assertions and Type Switches.
1. Type Assertions: val := i.(ConcreteType)
A Type Assertion extracts the underlying concrete value from an interface value:
1. Single-Value Assertion (Risky — Panics on Mismatch)
var x any = "hello"
str := x.(string) // OK: str is "hello"
num := x.(int) // 💥 PANIC: interface conversion: interface {} is string, not int2. Double-Value Assertion (The Safe “Comma-Ok” Idiom)
Always use the two-value form in production code to test types without crashing:
package main
import "fmt"
func process(val any) {
// Comma-ok idiom: ok is true if assertion succeeded, false otherwise
if s, ok := val.(string); ok {
fmt.Printf("String length: %d (value: %s)\n", len(s), s)
return
}
if n, ok := val.(int); ok {
fmt.Printf("Doubled integer: %d\n", n*2)
return
}
fmt.Println("Unknown or unhandled type!")
}
func main() {
process("cloud-native") // String length: 12 (value: cloud-native)
process(42) // Doubled integer: 84
process(3.14) // Unknown or unhandled type!
}2. Type Switches: Handling Multiple Types Cleanly
When handling multiple possible types, a Type Switch is much cleaner than chaining multiple if/else assertions:
package main
import "fmt"
func inspect(x any) {
// The type switch syntax assigns the unwrapped concrete value to 'v'
switch v := x.(type) {
case string:
fmt.Printf("String of length %d: %q\n", len(v), v)
case int, int64:
fmt.Printf("Integer: %v\n", v)
case bool:
fmt.Printf("Boolean flag: %t\n", v)
case []byte:
fmt.Printf("Byte buffer: %d bytes\n", len(v))
case nil:
fmt.Println("Value is literal nil!")
default:
fmt.Printf("Unexpected type %T: %v\n", v, v)
}
}
func main() {
inspect("golang")
inspect(100)
inspect(true)
inspect([]byte{0xDE, 0xAD})
inspect(nil)
}3. Real-World Use Case: Parsing JWT Claims or Generic JSON
In API authentication, JWT token claims are frequently parsed as map[string]any. You use type assertions to safely extract typed claims:
package main
import "fmt"
func extractUserID(claims map[string]any) (string, error) {
subRaw, ok := claims["sub"]
if !ok {
return "", fmt.Errorf("claim 'sub' missing from token")
}
sub, ok := subRaw.(string)
if !ok {
return "", fmt.Errorf("claim 'sub' must be a string, got %T", subRaw)
}
return sub, nil
}
func main() {
validClaims := map[string]any{"sub": "usr_9981", "admin": true}
userID, err := extractUserID(validClaims)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Authenticated User ID:", userID)
}4. any vs interface{}
Starting in Go 1.18, any was introduced as an official alias for interface{}:
// Both lines are 100% identical in Go:
var a interface{} = "legacy syntax"
var b any = "modern clean syntax"TIP
Use
anySparingly: Whileanycan hold anything, using it everywhere throws away Go’s compile-time type safety. Prefer specific interfaces (io.Reader) or Generics whenever possible.
Across the Wiki
- Go Interfaces — How interfaces are created and satisfied.
- Go Generics — Writing type-safe reusable code without needing
any. - Go Reflect — Inspecting arbitrary types dynamically when type switches aren’t enough.