Skip to content

Intermediate · Pointers, interfaces and errors · topic 18 of 46 · 30 min

any, type assertions and type switches

Explanation

any (another name for interface{}) can hold a value of any type. To get the concrete value back, use a type assertion v, ok := x.(string), or a type switch to handle several types. An interface value is nil only when both its type and value are empty, which causes the famous "nil error that is not nil" bug.

The empty interface: any

An interface with no methods is satisfied by every type, because every type has at least zero methods. It is written interface{}, and since Go 1.18 also as any, which means exactly the same. fmt.Println takes ...any, which is why it can print anything.

go
var x any
x = 42
x = "Asha"
x = []int{1, 2}
fmt.Println(x) // [1 2]
An any variable can hold different types over time.
Careful: any throws away type safety: the compiler can no longer check what is inside. Use it only at true boundaries (printing, decoding unknown JSON, some generic containers). Prefer real types, small interfaces or generics (module 5).

How an interface value works

An interface value is a pair: (concrete type, value). For x = 42 it is (int, 42). For x = "Asha" it is (string, "Asha"). This pair explains both type assertions and the nil gotcha below.

Type assertions

go
var x any = "UPI"

s := x.(string)        // OK: s is "UPI"
n, ok := x.(int)       // safe form: ok is false, n is 0
fmt.Println(s, n, ok)  // UPI 0 false

// m := x.(int)        // panic: interface conversion: interface {} is string, not int
Always use the two-value "comma ok" form unless you are certain of the type.

Type switches

go
func describe(v any) string {
	switch t := v.(type) {
	case int:
		return fmt.Sprintf("int %d", t)
	case string:
		return "string of length " + strconv.Itoa(len(t))
	case []int:
		return fmt.Sprintf("slice with %d items", len(t))
	case nil:
		return "nothing"
	default:
		return fmt.Sprintf("other type %T", t)
	}
}
Inside each case, t already has that case's type. v.(type) only works inside a switch.

You can also assert to an interface: if s, ok := v.(fmt.Stringer); ok { fmt.Println(s.String()) } checks whether the value has a String method.

The nil interface gotcha

An interface value is nil only when BOTH parts are empty: no type and no value. If you store a nil pointer of a concrete type in an interface, the type part is set, so the interface is NOT nil. This bites people most often with errors.

go
type MyErr struct{}
func (*MyErr) Error() string { return "boom" }

func mayFail(fail bool) error {
	var e *MyErr = nil
	if fail {
		e = &MyErr{}
	}
	return e // BUG: returns (type *MyErr, value nil), which is not a nil error
}

func main() {
	err := mayFail(false)
	fmt.Println(err == nil) // false (!)
}
The caller thinks something failed even though nothing did.
go
func mayFail(fail bool) error {
	if fail {
		return &MyErr{}
	}
	return nil // a real nil interface
}
The fix: return the literal nil for "no error". Never return a typed nil pointer as an error.
Tip: Rule of thumb: functions that return error should end with return nil on success, not with a variable of a concrete error type.

Decoding unknown JSON

When JSON is decoded into any, objects become map[string]any, arrays become []any and numbers become float64 (not int). You then use type assertions to dig in. When you know the shape, decode into a struct instead (module 7): it is safer and clearer.

go
var data any
json.Unmarshal([]byte(`{"name":"Asha","marks":91}`), &data)
m := data.(map[string]any)
marks := m["marks"].(float64)   // JSON numbers become float64
fmt.Println(m["name"], marks)   // Asha 91
Asserting m["marks"].(int) here would panic, because the value is a float64.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

any, type assertions and type switches · 1/7

any, assertions and type switches

Sometimes you need a variable that can hold anything. Go has any for that.

Prefer a video by a teacher? Find free videos on “golang type assertion type switch empty interface any nil interface” on YouTube ↗

In real life: Reading mixed values from a config file

A small CLI tool reads settings from a JSON file whose values might be numbers, strings or true/false. A type switch turns each value into a clear message and reports unexpected types instead of crashing, so a typo in the config file gives a friendly error rather than a panic.

go
package main

import "fmt"

func check(key string, v any) {
	switch t := v.(type) {
	case float64:
		fmt.Printf("%s is a number: %g\n", key, t)
	case string:
		fmt.Printf("%s is text: %q\n", key, t)
	case bool:
		fmt.Printf("%s is on/off: %t\n", key, t)
	default:
		fmt.Printf("%s has unsupported type %T\n", key, t)
	}
}

func main() {
	settings := map[string]any{"port": 8080.0, "city": "Pune", "debug": true, "tags": []any{"a"}}
	for _, k := range []string{"port", "city", "debug", "tags"} {
		check(k, settings[k])
	}
}
Prints port is a number: 8080, city is text: "Pune", debug is on/off: true, tags has unsupported type []interface {}.

Practice (0/4 done)

Task 1 · Question

What does this print? var x any = 3.5 _, ok := x.(int) fmt.Println(ok)

Task 2 · Question

What happens? var x any = "hi" n := x.(int) fmt.Println(n)

Task 3 · Question

func f() error { var p *os.PathError = nil; return p }. Is f() == nil?

Task 4 · Practice

Write func sizeOf(v any) int that returns: len for a string, len for a []int, the value itself for an int, and -1 for anything else. Use a type switch and test it with four different values.

Quick quiz

Question 1

1.What is any?

Question 2

2.Where can you write v.(type)?

Question 3

3.JSON number 91 decoded into an any becomes which type?

Question 4

4.When is an interface value equal to nil?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is any?

Answer: An alias for interface{}

Since Go 1.18, any is a predeclared alias for interface{}.

Where can you write v.(type)?

Answer: Only in a type switch

.(type) is allowed only in the header of a type switch.

JSON number 91 decoded into an any becomes which type?

Answer: float64

encoding/json decodes numbers into float64 when the target is an interface.

When is an interface value equal to nil?

Answer: When both its type and value parts are unset

A typed nil pointer inside an interface still makes the interface non-nil.

Before Go 1.22, what did for i := 0; i < 3; i++ { defer func() { fmt.Print(i) }() } print?Related

Answer: 333

All closures shared one i, which was 3 when they ran. Since Go 1.22 it prints 210.

From Go language interview questions

What is the type of &x when x is a float64?Related

Answer: *float64

& on a float64 gives a *float64.

From Pointers without fear

Which verb in fmt.Errorf wraps an error so errors.Is can find it?Related

Answer: %w

%w wraps; %v and %s only copy the text.

From Errors as values: wrap, Is and As

How does a type declare that it implements an interface?Related

Answer: It does not; having the methods is enough

Go interfaces are satisfied implicitly.

From Interfaces: behaviour, not family trees

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Pointers, interfaces and errors” before its exam: Errors as values: wrap, Is and As, panic and recover: only for the truly unexpected.
  2. Module 4 exam: Pointers, interfaces and errors · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Packages, testing and generics. 46 topics left in the course before the final exam.

← Previous: Interfaces: behaviour, not family trees