Go Tutorials: Go Type Assertions — Easy Basic to Advanced

1. What Is a Type Assertion?

A type assertion is used when you have a value stored inside an interface and you want to check or retrieve its real type.

The syntax is:

value.(Type)

Easy meaning:

"Is the value inside this interface really this Type?"

2. Simplest Example

package main

import "fmt"

func main() {

	// 'value' has type 'any'.
	//
	// any can hold values of different types.
	var value any

	// Store a string inside 'value'.
	value = "Hello Go"

	// Type assertion:
	//
	// value.(string)
	//
	// means:
	//
	// "I expect the value stored inside
	// this interface to be a string."
	text := value.(string)

	// 'text' is now a normal string variable.
	fmt.Println(text)

	// Output:
	//
	// Hello Go
}

The important line is:

text := value.(string)

Think of it as:

value contains something
        ↓
value.(string)
        ↓
"Is that something a string?"
        ↓
Yes → give me the string

3. Why Do We Need Type Assertions?

package main

import "fmt"

func main() {

	// Normal string variable.
	name := "Alice"

	// Go already knows 'name' is a string.
	// Therefore no type assertion is needed.
	fmt.Println(len(name))

	// --------------------------------

	// Here the variable type is 'any'.
	var value any = "Alice"

	// Go knows that 'value' is an interface,
	// but we want to work with the string
	// stored inside it.

	// Extract the string.
	text := value.(string)

	// Now text is a normal string.
	fmt.Println(len(text))

	// Output:
	//
	// 5
	// 5
}

4. Think of an Interface as a Box

package main

import "fmt"

func main() {

	// Imagine 'value' as a box.
	//
	// +----------------------+
	// | Type:  string        |
	// | Value: "Go"          |
	// +----------------------+

	var value any = "Go"

	// Type assertion asks:
	//
	// "Does this box contain a string?"
	text := value.(string)

	// Yes.
	// Therefore Go gives us the string.
	fmt.Println(text)

	// Output:
	//
	// Go
}

This is the easiest mental model:

Interface = Box

Type assertion = Check what is inside the box

5. What Happens If the Type Is Wrong?

package main

func main() {

	// The real value stored here is an int.
	var value any = 100

	// This would cause a runtime PANIC:
	//
	// text := value.(string)
	//
	// Why?
	//
	// Actual stored type:
	//
	// int
	//
	// Requested type:
	//
	// string
	//
	// We told Go:
	//
	// "I am sure this is a string."
	//
	// But it is not.

	_ = value
}

So this form:

text := value.(string)

means:

"I am certain this is a string."

If you are wrong, the program can panic.


6. Safe Type Assertion

This is the form you should learn first.

package main

import "fmt"

func main() {

	var value any = "Hello"

	// Safe type assertion returns TWO values.
	//
	// text = extracted string
	// ok   = whether the assertion succeeded
	text, ok := value.(string)

	// Check if the assertion failed.
	if !ok {
		fmt.Println("Value is not a string")
		return
	}

	// If we reach this line,
	// 'text' is definitely a string.
	fmt.Println(text)

	// Output:
	//
	// Hello
}

Remember this pattern:

value, ok := interfaceValue.(Type)

if !ok {
	// wrong type
	return
}

// safely use value

7. What Does ok Mean?

package main

import "fmt"

func main() {

	var value any = "Go"

	text, ok := value.(string)

	// Because value contains a string:
	//
	// text = "Go"
	// ok   = true

	fmt.Println(text)
	fmt.Println(ok)

	// Output:
	//
	// Go
	// true
}

If it fails:

package main

import "fmt"

func main() {

	var value any = 100

	text, ok := value.(string)

	// value contains int,
	// not string.
	//
	// Therefore:
	//
	// text = ""
	//
	// "" is the zero value of string.
	//
	// ok = false

	fmt.Printf("text = %q\n", text)
	fmt.Println("ok =", ok)

	// Output:
	//
	// text = ""
	// ok = false
}

8. Type Assertion with int

package main

import "fmt"

func main() {

	// Store an integer inside an interface.
	var value any = 25

	// Try to extract an int.
	number, ok := value.(int)

	if !ok {
		fmt.Println("Not an integer")
		return
	}

	// number is now a normal int.
	result := number * 2

	fmt.Println(result)

	// Output:
	//
	// 50
}

9. Type Assertion with a Struct

package main

import "fmt"

// Normal struct.
type User struct {
	Name string
	Age  int
}

func main() {

	// Store a User inside 'any'.
	var value any = User{
		Name: "Alice",
		Age:  30,
	}

	// Extract User from the interface.
	user, ok := value.(User)

	if !ok {
		fmt.Println("Value is not a User")
		return
	}

	// 'user' is now type User,
	// so its fields are available.
	fmt.Println(user.Name)
	fmt.Println(user.Age)

	// Output:
	//
	// Alice
	// 30
}

10. Important: User and *User Are Different

package main

import "fmt"

type User struct {
	Name string
}

func main() {

	// Store a POINTER to User.
	var value any = &User{
		Name: "Alice",
	}

	// This checks for User.
	//
	// But the stored type is *User.
	user1, ok1 := value.(User)

	fmt.Println(ok1)

	// Output:
	//
	// false

	_ = user1

	// --------------------------------

	// This checks for *User.
	//
	// That matches the actual stored type.
	user2, ok2 := value.(*User)

	fmt.Println(ok2)
	fmt.Println(user2.Name)

	// Output:
	//
	// true
	// Alice
}

Remember:

User

and:

*User

are different types.


11. Type Assertion Does NOT Convert Types

This is extremely important.

package main

import "fmt"

func main() {

	// The actual stored type is float64.
	var value any = 10.5

	// We ask:
	//
	// "Is the stored value an int?"
	number, ok := value.(int)

	// No.
	fmt.Println(number)
	fmt.Println(ok)

	// Output:
	//
	// 0
	// false
}

Type assertion does not mean:

Convert float64 to int

It means:

Check whether the value is already an int

To convert:

package main

import "fmt"

func main() {

	var value any = 10.5

	// Step 1:
	// Assert the REAL stored type.
	decimal, ok := value.(float64)

	if !ok {
		return
	}

	// Step 2:
	// Perform normal type conversion.
	number := int(decimal)

	fmt.Println(number)

	// Output:
	//
	// 10
}

So:

value.(float64)

is a type assertion.

But:

int(decimal)

is a type conversion.


12. Type Assertion vs Type Conversion

package main

func main() {

	// --------------------------
	// TYPE CONVERSION
	// --------------------------

	number := 10

	// Convert int into float64.
	decimal := float64(number)

	_ = decimal

	// --------------------------
	// TYPE ASSERTION
	// --------------------------

	var value any = "Go"

	// Extract/check the string already
	// stored inside the interface.
	text := value.(string)

	_ = text
}

Remember:

Type Conversion
---------------

int → float64

float64(number)


Type Assertion
--------------

interface → contained type

value.(string)

13. Real Use Case: map[string]any

You may see dynamic data written like this:

package main

import "fmt"

func main() {

	// Values can contain different types.
	data := map[string]any{
		"name": "Alice",
		"age":  30,
	}

	// data["name"] returns an 'any' value.
	nameValue := data["name"]

	// Extract string.
	name, ok := nameValue.(string)

	if !ok {
		fmt.Println("name is not a string")
		return
	}

	// Extract age.
	ageValue := data["age"]

	age, ok := ageValue.(int)

	if !ok {
		fmt.Println("age is not an integer")
		return
	}

	fmt.Println("Name:", name)
	fmt.Println("Age:", age)

	// Output:
	//
	// Name: Alice
	// Age: 30
}

This is one common use case for type assertions.


14. Checking Multiple Possible Types

You could do this:

package main

import "fmt"

func printValue(value any) {

	// Check string.
	if text, ok := value.(string); ok {
		fmt.Println("String:", text)
		return
	}

	// Check int.
	if number, ok := value.(int); ok {
		fmt.Println("Integer:", number)
		return
	}

	// Check bool.
	if flag, ok := value.(bool); ok {
		fmt.Println("Boolean:", flag)
		return
	}

	fmt.Println("Unknown type")
}

func main() {

	printValue("Go")
	printValue(100)
	printValue(true)

	// Output:
	//
	// String: Go
	// Integer: 100
	// Boolean: true
}

This works.

But when many types are possible, use a type switch.


15. Type Switch

package main

import "fmt"

func printValue(value any) {

	// value.(type) checks the concrete type
	// stored inside the interface.
	switch v := value.(type) {

	case string:

		// Here v is a string.
		fmt.Println("String:", v)

	case int:

		// Here v is an int.
		fmt.Println("Integer:", v)

	case bool:

		// Here v is a bool.
		fmt.Println("Boolean:", v)

	case float64:

		// Here v is a float64.
		fmt.Println("Float:", v)

	default:

		fmt.Println("Unknown type")
	}
}

func main() {

	printValue("Go")
	printValue(100)
	printValue(true)
	printValue(3.14)

	// Output:
	//
	// String: Go
	// Integer: 100
	// Boolean: true
	// Float: 3.14
}

Simple rule:

One possible type
-----------------

value, ok := data.(string)


Many possible types
-------------------

switch v := data.(type)

16. Type Assertion with Interfaces

Type assertions can also check whether a value supports another interface.

package main

import "fmt"

// Every Speaker can Speak().
type Speaker interface {
	Speak()
}

// Every Runner can Run().
type Runner interface {
	Run()
}

type Dog struct{}

func (Dog) Speak() {
	fmt.Println("Woof!")
}

func (Dog) Run() {
	fmt.Println("Dog is running")
}

func main() {

	// Variable only promises Speaker behavior.
	var speaker Speaker = Dog{}

	speaker.Speak()

	// Now ask:
	//
	// "Does the actual value inside speaker
	// also satisfy Runner?"
	runner, ok := speaker.(Runner)

	if !ok {
		fmt.Println("Cannot run")
		return
	}

	// Yes, Dog also has Run().
	runner.Run()

	// Output:
	//
	// Woof!
	// Dog is running
}

This is a very useful advanced use case.


17. Practical Use Case: Optional Capability

package main

import "fmt"

// Required behavior.
type Saver interface {
	Save()
}

// Optional additional behavior.
type Closer interface {
	Close()
}

type File struct{}

func (File) Save() {
	fmt.Println("Saving file")
}

func (File) Close() {
	fmt.Println("Closing file")
}

func process(s Saver) {

	// Every Saver can Save().
	s.Save()

	// But maybe not every Saver can Close().
	//
	// Check whether this particular value
	// also satisfies Closer.
	closer, ok := s.(Closer)

	if ok {
		closer.Close()
	}
}

func main() {

	file := File{}

	process(file)

	// Output:
	//
	// Saving file
	// Closing file
}

The assertion:

closer, ok := s.(Closer)

means:

"Does the concrete value inside s
also support Closer?"

18. Type Assertions Only Work on Interfaces

package main

func main() {

	// Normal string variable.
	name := "Alice"

	// This is INVALID:
	//
	// name.(string)
	//
	// Why?
	//
	// name is already a string.
	// It is not an interface value.

	_ = name

	// --------------------------

	// This IS an interface.
	var value any = "Alice"

	// Therefore a type assertion is allowed.
	text := value.(string)

	_ = text
}

19. Unsafe vs Safe Type Assertion

package main

func main() {

	var value any = "Go"

	// ------------------------------
	// UNSAFE / ONE-VALUE FORM
	// ------------------------------

	// Use when you are certain.
	text := value.(string)

	_ = text

	// If wrong:
	//
	// runtime panic


	// ------------------------------
	// SAFE / TWO-VALUE FORM
	// ------------------------------

	text2, ok := value.(string)

	if !ok {
		// Wrong type.
		return
	}

	_ = text2

	// If wrong:
	//
	// no panic
	// ok becomes false
}

For learning and most uncertain data, prefer:

value, ok := data.(Type)

20. Final Cheat Sheet

package main

func main() {

	// --------------------------------
	// Interface containing a string
	// --------------------------------

	var value any = "Go"


	// --------------------------------
	// Basic type assertion
	// --------------------------------

	text := value.(string)

	_ = text


	// --------------------------------
	// Safe type assertion
	// --------------------------------

	text2, ok := value.(string)

	if !ok {
		return
	}

	_ = text2


	// --------------------------------
	// Type assertion is NOT conversion
	// --------------------------------

	// Assertion:
	//
	// value.(string)
	//
	// means:
	//
	// "Is a string already inside value?"


	// --------------------------------
	// Type conversion
	// --------------------------------

	number := 10

	decimal := float64(number)

	_ = decimal


	// --------------------------------
	// Many possible types
	// --------------------------------

	switch v := value.(type) {

	case string:
		_ = v

	case int:
		_ = v

	case bool:
		_ = v
	}
}

The One Definition to Remember

// TYPE ASSERTION:
//
// actualValue, ok := interfaceValue.(ExpectedType)
//
// means:
//
// "Check whether the value stored inside this
// interface has the type I expect.
//
// If yes:
//     give me the value
//     ok = true
//
// If no:
//     give me the zero value
//     ok = false"

The Easiest Mental Model

// Imagine:
//
// var value any = "Alice"
//
//
// value is a BOX:
//
// +----------------------+
// | Type:  string        |
// | Value: "Alice"       |
// +----------------------+
//
//
// name, ok := value.(string)
//
// means:
//
// "Open the box.
//
// Is the value inside really a string?"
//
//
// YES:
//
// name = "Alice"
// ok   = true
//
//
// NO:
//
// name = ""
// ok   = false

In one sentence: A type assertion checks or extracts the actual value/type stored inside a Go interface.

Related Posts

Go Tutorials: Go Methods

Go methods become much easier once you understand one idea: A method is simply a function attached to a type. And the important rule is: A receiver…

Read More

Go Tutorials: Modules, Packages, Subpackages, Submodules, and Workspaces

This tutorial gives you one complete mental model for: The most important idea is: And sometimes: These two designs are very different. 1. What is a Go…

Read More

Go Tutorials: Numeric Types Beginner Tutorial

Go has four main families of numeric types: The simplest way to remember them is: Type family Stores Example int Whole numbers, positive or negative -10, 0,…

Read More

Go Tutorials: Go Testing, Benchmarking, and Profiling

These three topics become much easier once you separate the questions they answer: Topic Main question Main Go tool Result Testing Does my code work correctly? go…

Read More

Go Tutorials: Generics

1. What are Generics? Generics let you write one piece of code that works with multiple types while keeping Go’s compile-time type safety. Suppose you want a…

Read More

Go Concurrency Management Made Simple

This tutorial covers: The goal is: One concept → one purpose → one complete runnable example. Every example is independent. Save any example as: and run: 0….

Read More