Introduction: "What kind of beast is this?"
\n\nImagine: you are writing a Go function that takes an interface{}. Inside this function, you want to understand what kind of data came to you. A number? A string? Or maybe some custom struct? If you come from Python or JavaScript, where you can find out the type in one line (type() or typeof), then in Go you are in for a small surprise. Go is a strictly typed language, and you can't just "feel" the type of an object.
The question "How to find the type of an object in Go?" on Stack Overflow has gathered tens of thousands of views. This means that even experienced developers sometimes get confused. In this article, we will look at the top mistakes that beginners (and not only) make when determining a type in Go, and show how to avoid them. You will learn about reflect.TypeOf, fmt.Sprintf with %T, type assertion, and type switch.
\n\nMethod #1: The Simplest — fmt and %T (but with a catch)
\n\nThe fastest way to find out the type is to use the fmt package and the %T format. This works, but many forget that fmt is not a magic wand, but just a wrapper over reflect.
package main\n\nimport "fmt"\n\nfunc main() {\n var x interface{} = 42\n fmt.Printf("Type: %T\\n", x) // Type: int\n\n var s interface{} = "hello"\n fmt.Printf("Type: %T\\n", s) // Type: string\n}\n\n\nSimple, right? But here's the mistake: %T returns a string, not a type. You cannot perform any operations with it other than printing it to the screen. If you need to check whether an object is a number and do something with it — %T won't help you. This is the first mistake: using %T where branching logic is needed.
How to avoid it
\nUse %T only for debugging or logging output. For type checking in code, use type assertion or type switch (more on these below).
Method #2: The reflect Package — Powerful but Dangerous
\n\nThe reflect package is the gentleman's set for reflection in Go. The most popular method is reflect.TypeOf().
package main\n\nimport (\n "fmt"\n "reflect"\n)\n\nfunc main() {\n var x interface{} = 42\n t := reflect.TypeOf(x)\n fmt.Println(t) // int\n fmt.Println(t.Name()) // int\n fmt.Println(t.Kind()) // int\n}\n\n\nIt seems like the perfect solution. But here beginners make the second mistake: confusing Kind() and Name().
- \n
Name()— returns the type name (e.g.,int,string,MyStruct). \n Kind()— returns the base "kind" (e.g.,int,struct,slice). \n
For built-in types they match, but for custom types — they don't. For example, if you declare type MyInt int, then Name() will return MyInt, and Kind() will return int. If you only check Kind(), you might miss a custom type.
How to avoid it
\nAlways ask yourself: "Do I need the exact type or just the category?" If you need the exact type (e.g., for serialization) — use Name(). If you just need to understand that it's a number — use Kind().
Method #3: Type Assertion — A Classic (and a Panic Error)
\n\nType assertion is a way to "extract" a specific type from interface{}. It looks like this:
package main\n\nimport "fmt"\n\nfunc main() {\n var x interface{} = 42\n value := x.(int) // assert that x is an int\n fmt.Println(value + 10) // 52\n}\n\n\nWhat's the mistake? If x turns out