Type assertion is how you recover the concrete value stored inside an interface value, or test whether the dynamic type implements another interface. It is easy to confuse with type conversion (T(x)), which rewrites compatible concrete values. This page walks through syntax, the comma-ok idiom, panics, type switches, and when you do not need assertion at all. For the broader interface model, see interfaces in Go. For JSON decoding into map[string]any and similar shapes, see JSON unmarshal in Go.
Tested on: Go 1.22, 64-bit Linux. Single-result
x.(T)panics if the dynamic type does not match; preferv, ok := x.(T)or a type switch when the shape is not guaranteed.
Quick answer: extract a concrete type from an interface
Type assertion applies to interface values (any, interface{}, or a named interface type). It is not the same as ordinary type conversion between concrete types.
value, ok := x.(string)If x holds a string, ok is true and value is that string. If not, ok is false and value is the zero value of string—do not treat value as valid when ok is false.
package main
import (
"fmt"
)
func main() {
var x any = "golinuxcloud"
if s, ok := x.(string); ok {
fmt.Println("string:", s)
}
if _, ok := x.(int); !ok {
fmt.Println("not an int (no panic)")
}
}You should see the string line, then not an int (no panic). The single-result form x.(int) here would panic; the comma-ok branch avoids that.
What is type assertion in Go?
A type assertion asks: “Does this interface value’s dynamic type match T?” If yes, you get the stored value as type T (for concrete T), or as interface T when asserting to another interface whose method set is satisfied.
| Piece | Meaning |
|---|---|
x |
An expression of interface type |
T |
Asserted type (concrete or interface) |
x.(T) |
Single result: value of type T, or panic if the assertion fails |
v, ok := x.(T) |
Two results: ok reports success; no panic on failure |
Use assertion when you stored something behind any or a wide interface and now need methods, fields, or operators of a specific type.
Interface value and concrete type
An interface value has a dynamic type and a dynamic value. The dynamic type is the concrete type actually stored at runtime (string, *os.File, int, …). Type assertion does not invent a new value; it exposes the one already in the interface when the types line up.
| Stored as | Example dynamic value | Example dynamic type |
|---|---|---|
any |
"hello" |
string |
any |
42 |
int |
error |
wrapped *os.PathError |
*os.PathError |
io.Writer |
standard output | *os.File |
If your variable is already declared with a concrete type (var name string), you do not use type assertion—you already have a string.
Basic type assertion example
Direct type assertion
v := x.(T) is appropriate only when a mismatch would be a programming bug (you know the concrete type). Otherwise use comma-ok or a type switch.
Safe type assertion using comma-ok
v, ok := x.(T) sets ok to false on failure and v to the zero value of T. The comma-ok idiom is the usual pattern when data comes from JSON, plugins, or untyped maps.
package main
import (
"fmt"
"io"
"os"
)
func main() {
var w io.Writer = os.Stdout
f := w.(*os.File)
fmt.Println(f == os.Stdout)
var x any = 3.14
if s, ok := x.(string); !ok {
fmt.Printf("not a string, dynamic type is %T\n", x)
} else {
fmt.Println(s)
}
}The *os.File assertion succeeds because os.Stdout is a file. The any branch prints a message without panicking because the comma-ok form was used.
You can also assert to a narrower interface when the dynamic type’s method set is large enough:
package main
import (
"fmt"
"io"
"os"
)
func main() {
var w io.Writer = os.Stdout
rw := w.(io.ReadWriter)
fmt.Printf("%T\n", rw)
}Run prints *os.File: the value is still the same concrete type, now accessed through io.ReadWriter.
Why type assertion is needed
You reach for assertion when a value’s static type is wider than what you need at a boundary.
| Situation | Why assertion helps |
|---|---|
Parameter typed as any |
Callers may pass several concrete types; you pick one safely. |
map[string]any from json.Unmarshal |
Numbers, objects, and arrays arrive as float64, map[string]any, []any, etc. |
| Interface-based APIs | You only know io.Reader until you need *bytes.Reader behavior. |
| Custom errors | Inspect a concrete error type (often with errors.As, e.g. errors.As(err, &pathErr)). |
If the function already takes string or your own struct type, widen the design with generics or separate functions before sprinkling assertions everywhere.
Type assertion vs type conversion
Type assertion x.(T) |
Type conversion T(x) |
|
|---|---|---|
x must be |
Interface type | Expression with defined type |
| Meaning | “Is the dynamic type T?” |
“Rewrite x as T using language rules” |
| Mismatch | Single form: panic; comma-ok: ok == false |
Invalid conversion: compile error |
| Typical uses | any from JSON, io.Reader implementations |
float64(i), string(b) for []byte/[]rune, uintptr interop |
Rule of thumb: type assertion unwraps an interface; type conversion changes how a concrete value is viewed or widened/narrowed per the spec.
package main
import "fmt"
func main() {
var x any = "100"
n := 100
fmt.Println(float64(n) / 2) // conversion: float64 from int
if s, ok := x.(string); ok {
fmt.Println("asserted string:", s)
}
}float64(n) is conversion; x.(string) is assertion on any.
Type Assertion Panic and Safe Handling
The single-result form v := x.(T) panics if the dynamic type is not T (for concrete T), if T is an interface the dynamic type does not satisfy, or if x is a nil interface value (no dynamic type at all).
Use v, ok := x.(T) when the type is not guaranteed, and a type switch when many shapes are possible (see the next section).
Wrong-type panic
If x holds an int, x.(string) panics. The program below wraps that assertion in defer + recover so you can print the panic value without exiting the whole process—real code usually has no recover here and the goroutine crashes.
package main
import "fmt"
func assertStringWithRecover(x any) {
defer func() {
if r := recover(); r != nil {
fmt.Println("panic:", r)
}
}()
v := x.(string)
fmt.Println("value:", v)
}
func main() {
assertStringWithRecover(100)
}You should see one line starting with panic: interface conversion: and naming int vs string (exact text can vary slightly by Go release).
Comma-ok avoids the panic
package main
import "fmt"
func main() {
var x any = 100
v, ok := x.(string)
if !ok {
fmt.Println("x is not a string")
return
}
fmt.Println(v)
}You should see x is not a string; ok is false and v is the zero string—do not use v when ok is false.
Nil interface value
var x any is a nil interface: there is no dynamic value. A single-result assertion still panics; comma-ok reports failure instead.
package main
import "fmt"
func main() {
var x any
v, ok := x.(string)
if !ok {
fmt.Println("x does not contain a string")
return
}
fmt.Println(v)
}You should see x does not contain a string.
Asserting to an interface type
T in x.(T) may be an interface: Go checks whether the dynamic type implements T.
package main
import (
"fmt"
"io"
"strings"
)
func main() {
var x any = strings.NewReader("hello")
reader, ok := x.(io.Reader)
if !ok {
fmt.Println("x does not implement io.Reader")
return
}
fmt.Printf("%T implements io.Reader\n", reader)
}You should see *strings.Reader implements io.Reader (or equivalent).
| Assertion | Meaning |
|---|---|
x.(string) |
Dynamic type is exactly string |
x.(int) |
Dynamic type is exactly int |
x.(io.Reader) |
Dynamic value implements io.Reader |
Type switch in Go
A type switch dispatches on the dynamic type. The x.(type) form is only legal in a switch header.
package main
import "fmt"
func describe(x any) {
switch v := x.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
case map[string]int:
fmt.Println("map:", v)
default:
fmt.Printf("other: %T\n", x)
}
}
func main() {
describe("hello")
describe(map[string]int{"a": 1})
describe(3.14)
}You should see three lines: string, map, then other: float64. For general switch syntax, see Golang switch.
| Need | Prefer |
|---|---|
| One expected concrete type | v, ok := x.(T) or v := x.(T) if truly guaranteed |
| Several concrete branches | switch v := x.(type) |
| Exhaustive handling | type switch with default |
Common mistakes with type assertion
Using type assertion on a non-interface value
x.(T) requires x to be an interface. A plain string or int does not support assertion—use conversion or normal typing instead.
Ignoring the ok value
Using v := x.(string) when x is any from the network or JSON will eventually panic. Default to comma-ok unless you have just checked the type another way.
Confusing type assertion with type conversion
x.(string) asserts on an interface; string(x) converts x when the language allows (for example []byte to string). Different grammar, different rules.
Confusing concrete type with interface type
io.Reader describes behavior; *bytes.Buffer is a concrete type that satisfies it. Assertion reveals which concrete implementation you are holding.
Overusing any and assertions
Many assertions at the core of a package often mean the API should be more specific, use generics, or push the switch x.(type) to one boundary function.
Go type assertion cheat sheet
| Goal | Pattern |
|---|---|
Extract concrete T from interface |
v := x.(T) |
| Safe check | v, ok := x.(T) |
| Several possible types | switch v := x.(type) { ... } |
| Convert concrete types | T(x) |
| Wrapped errors | errors.As(err, &target) with target a pointer to the concrete error type |
Avoid panic on unknown any |
Comma-ok or type switch |
Which pattern should you use?
| Situation | Approach |
|---|---|
| You proved the type a line above | Single-result assertion |
| External or decoded data | Comma-ok or type switch |
| Many distinct types | Type switch with default |
| Need only one method | Widen the parameter to that interface instead of asserting |
Summary
Type assertion (x.(T)) is an interface operation: it inspects the dynamic type and yields the value as T, or fails with ok == false or a panic in the single-result form. Comma-ok and type switches are the safe tools when the dynamic type is uncertain—common with any and JSON. Type conversion (T(x)) solves a different problem on concrete values. For edge cases (nil interfaces, asserting to interface types), see the language spec: Type assertions and Type switches.

