Golang Type Assertion: Interface, Concrete Type, and Type Switch Examples

Learn how type assertion works in Go on interface values: concrete types, x.(T) vs comma-ok, panic rules, type switches, and how assertion differs from type conversion.

Published

Updated

Read time 9 min read

Reviewed byDeepak Prasad

Golang Type Assertion: Interface, Concrete Type, and Type Switch Examples

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; prefer v, 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.

go
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.

go
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)")
	}
}
Output

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.

go
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)
	}
}
Output

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:

go
package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	var w io.Writer = os.Stdout
	rw := w.(io.ReadWriter)
	fmt.Printf("%T\n", rw)
}
Output

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.

go
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)
	}
}
Output

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.

go
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)
}
Output

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

go
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)
}
Output

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.

go
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)
}
Output

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.

go
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)
}
Output

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.

go
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)
}
Output

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.


References

Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …